102

I'm changing some classes of mine from an extensive use of getters and setters to a more pythonic use of properties.

But now I'm stuck because some of my previous getters or setters would call the corresponding method of the base class, and then perform something else. But how can this be accomplished with properties? How to call the property getter or setter in the parent class?

Of course just calling the attribute itself gives infinite recursion.

class Foo(object):

    @property
    def bar(self):
        return 5

    @bar.setter
    def bar(self, a):
        print a

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return self.bar # --> recursion!

    @bar.setter
    def bar(self, c):
        # perform the same action
        # as in the base class
        self.bar = c    # --> recursion!
        # then do something else
        print 'something else'

fb = FooBar()
fb.bar = 7
spinkus
  • 7,694
  • 4
  • 38
  • 62
UncleZeiv
  • 18,272
  • 7
  • 49
  • 77

7 Answers7

114

You might think you could call the base class function which is called by property:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar(self)

Though this is the most obvious thing to try I think - it does not work because bar is a property, not a callable.

But a property is just an object, with a getter method to find the corresponding attribute:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)
CivFan
  • 13,560
  • 9
  • 41
  • 58
David Cournapeau
  • 78,318
  • 8
  • 63
  • 70
  • Why should I call `Foo.bar.fset(self, c)` in an inherited setter? Why not `Foo.bar.fset(c)` - without the "self" -I thought this is implicitely passed? – nerdoc Nov 13 '13 at 19:53
  • 2
    I get a TypeError: 'property' object is not callable – hithwen Dec 09 '13 at 08:13
  • @nerdoc where does the `self` get implies from the chain `Foo.bar.fset`? – Tadhg McDonald-Jensen Mar 06 '17 at 20:58
  • Just a thought - AFAIK self is always implicitly passed. If you do a `foo = Foo()` \ `foo.bar(c)` there is no *self* passed, but *bar()* receives it from Python. I'm no Python expert, more or less a beginner too. It's just a thought. – nerdoc Mar 07 '17 at 21:45
  • `self` is only implicitly passed when the method is called on an instance of a class. For example, if I have a class `A` with a method `b(self, arg)`, and I create an instance `c = A()`, then calling `c.b(arg)` is equivalent to `A.b(c, arg)` – TallChuck Nov 07 '18 at 21:27
  • I thought I'd add in case someone has a reason not to want to call the first base class by name even if it obfuscates the code... `Foo` is `self.__class__.__bases__[0]` (handly trick also for `super(self.__class__.__bases__[1], self)`. – Matteo Ferla May 03 '20 at 10:01
69

super() should do the trick:

return super().bar

In Python 2.x you need to use the more verbose syntax:

return super(FooBar, self).bar
Neuron
  • 5,141
  • 5
  • 38
  • 59
Pankrat
  • 5,206
  • 4
  • 31
  • 37
  • 2
    TypeError: super() takes at least 1 argument (0 given) – Aaron Maenpaa Jun 20 '09 at 11:49
  • 4
    I guess (well, judging by the link :P), this answer is python3-related. In python3 super() can take zero arguments, yes. – shylent Jun 20 '09 at 12:01
  • super() works without arguments in Python 3 and is equivalent to super(MyClass, self) but more readable in my book. – Pankrat Jun 20 '09 at 12:02
  • In python 2.7 I get AttributeError: 'super' object has no attribute 'bar' – hithwen Dec 09 '13 at 09:24
  • 14
    super().bar seems to work fine for the getter, but doesn't work for assignment through the base property in an overridden setter. If I do super().bar = 3 I get AttributeError: 'super' object has no attribute 'bar' – Rob Smallshire Jan 26 '14 at 20:17
  • 1
    Good point Rob, didn't know that. Here's more information: http://stackoverflow.com/questions/10810369/python-super-and-setting-parent-class-property – Pankrat Jan 28 '14 at 09:40
  • 2
    While this works for getting, it fails for setting with an `AttributeError`. – Ethan Furman Sep 08 '16 at 20:00
  • 1
    The above works for the getter. For the setter: super(self.__class__, self.__class__).bar.__set__(self, value) # See https://bugs.python.org/issue14965 – michael Jan 28 '20 at 22:26
30

There is an alternative using super that does not require to explicitly reference the base class name.

Base class A:

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

    @property
    def prop(self):
        return self._prop

    @prop.setter
    def prop(self, value):
        self._prop = value

class B(A):
    # we want to extend prop here
    pass

In B, accessing the property getter of the parent class A:

As others have already answered, it's:

super(B, self).prop

Or in Python 3:

super().prop

This returns the value returned by the getter of the property, not the getter itself but it's sufficient to extend the getter.

In B, accessing the property setter of the parent class A:

The best recommendation I've seen so far is the following:

A.prop.fset(self, value)

I believe this one is better:

super(B, self.__class__).prop.fset(self, value)

In this example both options are equivalent but using super has the advantage of being independent from the base classes of B. If B were to inherit from a C class also extending the property, you would not have to update B's code.

Full code of B extending A's property:

class B(A):
    @property
    def prop(self):
        value = super(B, self).prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(B, self.__class__).prop.fset(self, value)

One caveat:

Unless your property doesn't have a setter, you have to define both the setter and the getter in B even if you only change the behaviour of one of them.

RasmusN
  • 147
  • 1
  • 12
Maxime R.
  • 9,621
  • 7
  • 53
  • 59
  • Hello, this is very helpful. I have a follow-up question to this. This setup works well; however, it stops working when I set __init__ for B to define additional properties. Is there a way to have a separate __init__ for B? Thank you – Sang Nov 30 '17 at 20:16
  • Could you please explain how `super(B, self.__class__)` works exactly with `super(class, class)`? Where is it documented? – Art Nov 02 '18 at 18:55
  • I've suggested some small tweaks to this answer in [my answer](https://stackoverflow.com/a/60766191/102441) – Eric Mar 19 '20 at 22:52
4

try

@property
def bar:
    return super(FooBar, self).bar

Although I'm not sure if python supports calling the base class property. A property is actually a callable object which is set up with the function specified and then replaces that name in the class. This could easily mean that there is no super function available.

You could always switch your syntax to use the property() function though:

class Foo(object):

    def _getbar(self):
        return 5

    def _setbar(self, a):
        print a

    bar = property(_getbar, _setbar)

class FooBar(Foo):

    def _getbar(self):
        # return the same value
        # as in the base class
        return super(FooBar, self)._getbar()

    def bar(self, c):
        super(FooBar, self)._setbar(c)
        print "Something else"

    bar = property(_getbar, _setbar)

fb = FooBar()
fb.bar = 7
workmad3
  • 25,101
  • 4
  • 35
  • 56
  • This works fine if you write the base class. But what if you extend a third-party base class which uses the same name for the property and the getter? – akaihola Jan 15 '13 at 14:02
4

Some small improvements to Maxime's answer:

  • Using __class__ to avoid writing B. Note that self.__class__ is the runtime type of self, but __class__ without self is the name of the enclosing class definition. super() is a shorthand for super(__class__, self).
  • Using __set__ instead of fset. The latter is specific to propertys, but the former applies to all property-like objects (descriptors).
class B(A):
    @property
    def prop(self):
        value = super().prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(__class__, self.__class__).prop.__set__(self, value)
Eric
  • 95,302
  • 53
  • 242
  • 374
  • What''s unclear to me is the difference between `super(__class__, self)` and `super(__class__, self.__class__)`, and why we need the latter for setting property of the ancestor class? – CharlesB Jan 13 '21 at 16:06
  • 1
    From the `help()` for `super`, there are two overloads - `super(type, obj)`, which is the first case, and `super(type, type2)`, which is the second case. We're accessing `.prop` as a class attribute not an instance attribute, which is why the latter is needed. – Eric Jan 13 '21 at 16:19
  • 1
    `super(type(self), type(self)).setter.fset(self, value)` doesn't work adequately with multiple inheritance. Try my solution `duper(super()).setter = value`: https://gist.github.com/willrazen/bef3fcb26a83dffb6692e5e10d3e67ac – Will Razen May 21 '21 at 02:10
  • In what case does it not work adequately? – Eric May 21 '21 at 08:19
0

You can use the following template:

class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1
  
class Child(Parent):

    #getter
    @property
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)

Note! All of the property methods must be redefined together. If do not want to redefine all methods, use the following template instead:

class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1


class Child(Parent):

    #getter
    @Parent.prop1.getter
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @Parent.prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @Parent.prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)
-4
    class Base(object):
      def method(self):
        print "Base method was called"

    class Derived(Base):
      def method(self):
        super(Derived,self).method()
        print "Derived method was called"

    d = Derived()
    d.method()

(that is unless I am missing something from your explanation)

shylent
  • 10,076
  • 6
  • 38
  • 55