joaocandre
joaocandre

Reputation: 1745

Acessing base class property when overriding

Considering the following basic Python classes:

class Foo(object):

    def __init__(self, a1):
        self._att1 = a1

    @property
    def att1(self):
        return self._att1

    @att1.setter
    def att1(self, a):
        try:
            self._att1 = a
        except TypeError:
            # handle exception
            print("Not allowed")

class Bar(Foo):
    def __init__(self):
        Foo.__init__(self, 100)

    @Foo.att1.setter
    def att1(self, a):
        # self.att1 = a * 0.5  # RecursionError: maximum recursion depth exceeded
        # Foo.att1 = a * 0.5  # attribute is not changed (base instances are though (?))
        # Foo.att1(self,a)  # TypeError: 'property' object is not callable
        super().att1.__set__(a * 0.5)   # AttributeError: 'int' object has no attribute '__set__'
        # ... some other additional code ...

a = Foo(5)
b = Bar()
b.att1 = 700

print(a.att1)
print(b.att1)

What would be syntax for calling base property setter from within its override on the child class? I understand I could set self._att1 directly, but I would like to avoid that because I would then need to repeat exception handling code. This but a simple example, and I have more complex cases where the base class implements some additional operations on the attribute, and I would like to avoid repeating the same code on the derived class property setter.

Upvotes: 1

Views: 71

Answers (1)

Olvin Roght
Olvin Roght

Reputation: 7812

Code:

class Foo:
    def __init__(self, a1):
        self._att1 = a1

    @property
    def att1(self):
        return self._att1

    @att1.setter
    def att1(self, a):
        if not isinstance(a, int):
            print("Not allowed")
        else:
            self._att1 = a


class Bar(Foo):
    def __init__(self):
        Foo.__init__(self, 100)

    @Foo.att1.setter
    def att1(self, a):
        Foo.att1.fset(self, a * 2)


c = Bar()
print(c.att1)
c.att1 = 10
print(c.att1)
c.att1 = "some string"

Output:

100
20
Not allowed

UPD.

Following @chepner advice, I've decided to add some explanation.

When you're using decorator @Foo.att1.setter it doesn't work like you expected.

In docs you can see 2 examples of declaring properties: assign class variable using property() function and use @property decorator. Both this methods are equialent, but I find first much more obvious in case of demonstration how does provided code works.

Let's rewrite class declarations using property() function instead decorators:

class Foo:
    def __init__(self, a1):
        self._att1 = a1

    def getatt1(self):
        return self._att1

    def setatt1(self, a):
        if not isinstance(a, int):
            print("Not allowed")
        else:
            self._att1 = a

    att1 = property(getatt1, setatt1)

class Bar(Foo):
    def __init__(self):
        super().__init__(100)

    def setatt1(self, a):
        Foo.att1.fset(self, a * 2)

    att1 = property(Foo.getatt1, setatt1)

As you see, you don't override property, you're creating new with same name, which shadow base class property. You can prove it using this code:

print(f"Foo.att1 is Bar.att1: {Foo.att1 is Bar.att1}")

In both declarations it'll return False, which means that property objects of this classes aren't same.

Upvotes: 3

Related Questions