Emanuel Ey
Emanuel Ey

Reputation: 2844

Access superclass' property setter in subclass

I have a SuperClass which defines a property and it's setter, like so:

class A(object):
    def __init__(self):
        self._mode = None

    @property
    def mode(self):
        # to be overriden in subclass to implement the actual getter code
        raise NotImplementedError

    @mode.setter
    def mode(self, value):
        # common assertions and input validations
        self._set_mode(value)

    def _set_mode(self, value):
        # to be overriden in subclass to implement the actual setter code
        raise NotImplementedError


class B(A):
    @property
    def mode(self):
        return self._mode

    def _set_mode(self, value):
        self._mode = value


obj = B()
obj.mode = 'test'

Which raises

obj.mode = 'test'
AttributeError: can't set attribute

It would seem that I have to register a setter in B. I'd usually do this like @A.mode.setter, but that doesn't quite apply here as I don't actually want to define a new setter in B, just re-use the one from A.
Does anyone have a hint on how to solve this? Might be trivial, but I'm not seeing it right now :/

Upvotes: 7

Views: 4761

Answers (4)

pbsds
pbsds

Reputation: 251

A workaround to the cpython bug is to fetch the setter from the parent class manually

ParentClass.some_property.fset(self, value)

Example:

>>> class A:
...     @property
...     def mode(self):
...         return 2
...     
...     @mode.setter
...     def mode(self, value):
...         print("A", value)
... 
>>> class B(A):
...     @property
...     def mode(self):
...         return super().mode + 1
...     
...     @mode.setter
...     def mode(self, value):
...         print("B", value)
...         A.mode.fset(self, value + 1)
... 
>>> a = A()
>>> b = B()
>>> print(f"{a.mode = }")
a.mode = 2
>>> print(f"{b.mode = }")
b.mode = 3
>>> a.mode = 5
A 5
>>> b.mode = 5
B 5
A 6

If you want it parent-agnostic, then look through self.__class__.__bases__.

Upvotes: 0

ganiular
ganiular

Reputation: 629

It works for me this way

class A:
    @property
    def mode(self):
        raise NotImplemented
    
    @mode.setter
    def mode(self, value):
        raise NotImplemented

class B(A):
    @A.mode.setter
    def mode(self, value):
        # your setter implement here

Upvotes: 1

arseniiv
arseniiv

Reputation: 128

Analogously with using mode.setter inside A’s definition, this answer to a related question suggests to use a property of a base class to define a property on a subclass like so:

class B(A):
    @A.mode.getter              # only this line is changed!
    def mode(self):
        return self._mode

    def _set_mode(self, value):
        self._mode = value

Here, mode.setter will be the same as it was for A, but we’ve replaced the getter.

Upvotes: 3

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

the getter and setter are stored as attributes of the property object (respectively as .fget and .fset), so as soon as you overload the property in a child class you most explicitely provide both getter and setters, ie:

class B(A):
    @property
    def mode(self):
        return self._mode

    @mode.setter
    def mode(self, value):
        self._mode = value

So if you want to make the getter and/or setter overloadable without having to redeclare the property, you have to define a _get_mode method and make your property's getter delegate to this method, just like you did for the setter.

class A(object):
    def __init__(self):
        self._mode = None

    @property
    def mode(self):
        return self._get_mode()

    def _get_mode(self):
        # to be overriden in subclass to implement the actual getter code
        raise NotImplementedError

    @mode.setter
    def mode(self, value):
        # common assertions and input validations
        self._set_mode(value)

    def _set_mode(self, value):
        # to be overriden in subclass to implement the actual setter code
        raise NotImplementedError


class B(A):

    def _get_mode(self):
        return self._mode

    def _set_mode(self, value):
        self._mode = value

Upvotes: 7

Related Questions