Alex
Alex

Reputation: 1024

How to decorate a parent property's getter or setter?

I have defined a property area and its getter in a parent class Shape. In a child class Square I would like to expand the getter functionality of area. I wrote something which doesn't work:

version 1

class Shape:
    def __init__(self):
        self._area = None

    @property
    def area(self):
        print('hello parent')
        return self._area
    
    # setter of `area` could be also defined here; hence in the child
    # I would like to keep `area`, only decorating its getter.

class Square(Shape):
    def __init__(self):
        super(Square, self).__init__()

        area = Shape.area.getter(self.dosomething())

    def dosomething(self):
        def new_func():
            print('hello child')
            Shape.area.fget(self)
        return new_func

sq = Square()

When I run the code, I don't get the 'hello child':

>>> sq.area
hello parent

There are some obvious problems with my code above. For example, in Square I should have

self.area = Shape.area.getter(self.dosomething())

rather than no self. Then I would need to define the setter of area in parent...

version 2

After some fiddling, I came up with the following code,

class Shape:
    def __init__(self):
        self._area = None

    @property
    def area(self):
        print('hello parent')
        return self._area


class Square(Shape):
    def __init__(self):
        super(Square, self).__init__()

    @Shape.area.getter
    def area(self):
        print('hello child')
        Shape.area.fget(self)


sq = Square()

This time, it seems to do what I want:

>>> sq.area
hello child
hello parent

However, my ide tells me the line def area(self): in Square "overrides method in Shape". Furthermore, IDE also says in Shape.area.fget(self), self is an unexpected argument. Even though the code runs without error.

It feels like version 2 is a hack. Why is my ide complaining? I thought in version 2 I did not define a brand-new area, just decorating the inherited area's fget, no?

Upvotes: 2

Views: 195

Answers (2)

Alex
Alex

Reputation: 1024

After fiddling I arrive at this solution:

class Shape:
    def __init__(self):
        self._area = None

    @property
    def area(self):
        print('hello parent')
        return self._area

    @area.setter
    def area(self, val):
        print('parent setter')
        self._area = val


class Square(Shape):
    def __init__(self):
        super(Square, self).__init__()

    @Shape.area.getter
    def area(self):
        print('hello child')
        return super().area

    @area.setter
    def area(self, val):
        print('setter in child')
        Shape.area.fset(self, val)  # IDE complains that val is unexpected. Why?


sq = Square()

It does what I wanted, with a remaining issue: ide complaines that val is unexpected in the setter part in Square (noted in the code). This runs without problems though. Getting rid of self in that line raises error when running the code. I don't understand this. Feels like this is a hack.

I think the conclusion I arrive is that you are not supposed to expand on parent class's properties.

Upvotes: 0

blhsing
blhsing

Reputation: 106553

If you want to make the getter of the child class' area property make use of the parent's area property and expand on its value, in which case the child's getter function should call super() to access the parent's property. As for making the child's setter make use of the parent's setter you would have to access the parent's setter via the fset attribute of the parent class' property:

class Shape:
    def __init__(self):
        self._area = 'hello parent'

    @property
    def area(self):
        return self._area

    @area.setter
    def area(self, value):
        self._area = value

class Square(Shape):
    @property
    def area(self):
        return super().area + ' and child'

    @area.setter
    def area(self, value):
        Shape.area.fset(self, 'very ' + value)

sq = Square()
print(sq.area)
sq.area = 'new parent'
print(sq.area)

This outputs:

hello parent and child
very new parent and child

Alternatively, you can define new getter and setter methods in the child class and call property on them:

class Square(Shape):
    def area_getter(self):
        return super().area + ' and child'

    def area_setter(self, value):
        Shape.area.fset(self, 'very ' + value)

    area = property(area_getter, area_setter)

Upvotes: 1

Related Questions