Håkon Marthinsen
Håkon Marthinsen

Reputation: 111

Python: Unexpected behaviour when overriding properties

I would like to inherit from a class containing a property x, and then make this property read-only in the child class by overriding the setter. This does not work if __init__ in the parent class uses the original setter. Consider the following code.

class Parent:
    def __init__(self, x=1):
        # I want the following line to use the setter defined in the Parent
        # class, even when __init__ is called from Child using super.
        self.x = x
        # Other initialization of Parent goes here.

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        """Check that x is non-negative."""
        if value < 0:
            raise ValueError("x must be non-negative.")
        self._x = value


class Child(Parent):

    def __init__(self):
        super().__init__()  # Need this for initialization.

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        """x can only be written to implicitly by setting y."""
        self._y = value
        self._x = abs(value)

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        raise AttributeError("Illegal access to x")

If I now try to instantiate Child, I get AttributeError: Illegal access to x because when the line self.x = x is called, the x setter of Child is called instead of the x setter of Parent. How can I get it to use the setter of Parent in a Pythonic way?

To be clear, when self.x = ... appears in a method of Parent, it should always make use of the x setter in Parent, and when self.x = ... appears in a method of Child, it should always make use of the x setter in Child, and thus raise an exception.

Upvotes: 0

Views: 102

Answers (1)

H&#229;kon Marthinsen
H&#229;kon Marthinsen

Reputation: 111

I managed to solve the problem myself by swapping out

self.x = x

in Parent.__init__ with

Parent.x.fset(self, x)

I can also get rid of

@property
def x(self):
    return self._x

in Child if instead of @x.setter, I use @Parent.x.setter.

Upvotes: 1

Related Questions