AttributeError trying to define a setter for an inherit attribute in a subclass

62 views Asked by At

I have defined a base class using the dataclass decorator and I want to override the setter for one of the attributes in a derived class. I tried to override the setter by implementing a method in the derived class:

from dataclasses import dataclass, field

@dataclass(repr=False, eq=False)
class SimpleUrl:

    id: int  # pylint: disable=invalid-name
    url: str

    def __eq__(self, ins: object) -> bool:
        return isinstance(ins, SimpleUrl) and (self.url == ins.url)

    def __iter__(self) -> iter:
        return iter((self.id, self.url))


@dataclass(repr=False, eq=False)
class FullUrl(SimpleUrl):

    ssl: bool = field(init=False, default=None)

    @SimpleUrl.url.setter
    def url(self, url: str) -> None:
        self.url = url
        self.ssl = url.startswith('https://')

fu = FullUrl('https://www.google.com')
print(fu.ssl)

When I try to run this example I obtain the following error message:

Exception has occurred: AttributeError
type object 'SimpleUrl' has no attribute 'url'
  File "D:\projects\tests\test_urls.py", line 22, in FullUrl
    @SimpleUrl.url.setter
     ^^^^^^^^^^^^^
  File "D:\projects\tests\test_urls.py", line 18, in <module>
    class FullUrl(SimpleUrl):
AttributeError: type object 'SimpleUrl' has no attribute 'url'
1

There are 1 answers

0
Jan Willem On BEST ANSWER

You can override the __setattr__ method of the derived class and in there create the desired behavior. See an example below.

In this example I used the value 0 for the id parameter.

from dataclasses import dataclass, field

@dataclass(repr=False, eq=False)
class SimpleUrl:

    id: int  # pylint: disable=invalid-name
    url: str

    def __eq__(self, ins: object) -> bool:
        return isinstance(ins, SimpleUrl) and (self.url == ins.url)

    def __iter__(self) -> iter:
        return iter((self.id, self.url))


@dataclass(repr=False, eq=False)
class FullUrl(SimpleUrl):

    ssl: bool = field(init=False, default=None)

    def __setattr__(self, prop, val):
        super().__setattr__(prop, val)

        # Set ssl variable if url is set
        if prop == "url":
            super().__setattr__('ssl', val.startswith('https://'))


fu = FullUrl(0, 'https://www.google.com')
print(fu.ssl)

Returns:

>>> True

In the same __setatrr__ override you could also raise an error if someone tries to change the state of ssl by hand. If you change your override to:

    def __setattr__(self, prop, val):
        if prop == 'ssl':
            msg = "ssl is an immutable attribute."
            raise AttributeError(msg)

        super().__setattr__(prop, val)
        if prop == "url":
            super().__setattr__('ssl', val.startswith('https://'))

The user can now no longer freely change the ssl variable. If the user tries to set ssl:

fu = FullUrl(0, 'https://www.google.com')
fu.ssl = False

Returns:

Traceback (most recent call last):
  File "...py", line 33, in <module>
    fu.ssl = False
  File "...py", line 24, in __setattr__
    raise AttributeError(msg)
AttributeError: ssl is an immutable attribute.