4

In my code class A has a property, but class B doesn't inherit it. Does @property support inheritance? Or is it my fault?

class A(object):

    def __init__(self):
        self._x = 100

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

    @x.setter
    def x(self, v):
        self._x = v


class B(A):

    @x.setter
    def x(self, v):
        self._x = v

The error message is as below:

Traceback (most recent call last):
  File "test.py", line 9, in <module>
    class B(A):
  File "test.py", line 10, in B
    @x.setter
NameError: name 'x' is not defined
Ryan
  • 345
  • 3
  • 11
  • 2
    It should be `@A.x.setter`. `x` is under the namespace `A` here. – Ashwini Chaudhary Jul 11 '16 at 07:40
  • You can inherit a property, but you can not modify it independently. If you inherit `x` and add a setter, the original will have it, too. You can of cause define the property as `x` again to get an independent version. – Klaus D. Jul 11 '16 at 07:42
  • So `x` is read-only in the parent but read-write in the child? – jonrsharpe Jul 11 '16 at 07:42
  • @KlausD. that's not what happens when I test the answer below; I get an `AttributeError` when trying e.g. `A().x = 1`, as expected. – jonrsharpe Jul 11 '16 at 07:55
  • @jonrsharpe sorry, in orginal code, the class A has `@property` and `@setter` while the B only has `@setter`, and the error occurs. To simplify the problem I just delete the `@setter` of A without thinking too much, it's my fault and the question has been corrected. – Ryan Jul 11 '16 at 08:02
  • Ah, in that case @KlausD. is correct, and overriding the setter will affect the superclass behaviour; I've added an answer accordingly. – jonrsharpe Jul 11 '16 at 08:10

2 Answers2

6

The NameError is because x isn't in the global scope; it's defined within A's class namespace, so you need to access it there explicitly by using A.x. This is a pretty common mistake, see e.g. python subclass access to class variable of parent.

With properties, though, it can get slightly more complex. If you add a new setter in B, then there is no problem using A.x.setter as shown in Dean's answer. However, if you override an existing setter, you will also alter A's behaviour in doing so. I doubt this is the behaviour that you want.

Instead, in this case, you need to create a new property in the subclass with A's getter and a new setter. I think that this is this easiest way to achieve that, with minimal repetition:

class B(A):

    x = property(A.x.__get__)  # new property with same getter

    @x.setter  # new setter on new property
    def x(self, v):
        ...

Note the use of property without the @ syntactic sugar, as I'm not using it to decorate a new method. If you want to access A's setter from B's setter, you can do so with super().x.__set__(whatever).

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
3

It just doesn't know where to get x from. You can do this instead:

class A(object):
    def __init__(self):
        self._x = 100

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

class B(A):
    @A.x.setter
    def x(self, v):
        self._x = v
Dean Fenster
  • 2,345
  • 1
  • 18
  • 27
  • 1
    *"When there is no setter. A().x = 1 will just override the property"* - what? Are you trying this in Python 2.x? Without a setter a property is read-only, so gives `AttributeError: can't set attribute` – jonrsharpe Jul 11 '16 at 07:51
  • @jonrsharpe, You're right. I changed A to a new-style class, so now it's the same for python2 and python3 – Dean Fenster Jul 11 '16 at 07:54