python properties and inheritance
Asked Answered
E

10

64

I have a base class with a property which (the get method) I want to overwrite in the subclass. My first thought was something like:

class Foo(object):
    def _get_age(self):
        return 11

    age = property(_get_age)


class Bar(Foo):
    def _get_age(self):
        return 44

This does not work (subclass bar.age returns 11). I found a solution with an lambda expression which works:

age = property(lambda self: self._get_age())

So is this the right solution for using properties and overwrite them in a subclass, or are there other preferred ways to do this?

Exercise answered 26/10, 2008 at 2:49 Comment(0)
A
72

I simply prefer to repeat the property() as well as you will repeat the @classmethod decorator when overriding a class method.

While this seems very verbose, at least for Python standards, you may notice:

1) for read only properties, property can be used as a decorator:

class Foo(object):
    @property
    def age(self):
        return 11

class Bar(Foo):
    @property
    def age(self):
        return 44

2) in Python 2.6, properties grew a pair of methods setter and deleter which can be used to apply to general properties the shortcut already available for read-only ones:

class C(object):
    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value
Azilian answered 26/10, 2008 at 10:50 Comment(3)
Wait, so the proposed solution to getting behaviour defined in a parent class is to re-implement it? Doesn't that seem like a blatant violation of DRY (don't repeat yourself)?Immorality
This fails in the case where you only want to alter the behavior of either the getter or setter but not both. If you alter only the getter, you will get an AttributeError when trying to use the setter. Altering just the setter pretty much just doesn't work. The only way that defining it works (assuming the getter is not redefined) is if you use @<super class>.<property name>.setter however this is incorrect as it will modify the property of the superclass and not of the subclass which is definitely not what you want.Scofflaw
Not true, I've provided a case below where only the setter is altered (although either getter, setter, or both could be altered if desired).Exfoliation
E
22

I don't agree that the chosen answer is the ideal way to allow for overriding the property methods. If you expect the getters and setters to be overridden, then you can use lambda to provide access to self, with something like lambda self: self.<property func>.

This works (at least) for Python versions 2.4 to 3.6.

If anyone knows a way to do this with by using property as a decorator instead of as a direct property() call, I'd like to hear it!

Example:

class Foo(object):
    def _get_meow(self):
        return self._meow + ' from a Foo'
    def _set_meow(self, value):
        self._meow = value
    meow = property(fget=lambda self: self._get_meow(),
                    fset=lambda self, value: self._set_meow(value))

This way, an override can be easily performed:

class Bar(Foo):
    def _get_meow(self):
        return super(Bar, self)._get_meow() + ', altered by a Bar'

so that:

>>> foo = Foo()
>>> bar = Bar()
>>> foo.meow, bar.meow = "meow", "meow"
>>> foo.meow
"meow from a Foo"
>>> bar.meow
"meow from a Foo, altered by a Bar"

I discovered this on geek at play.

Exfoliation answered 16/1, 2013 at 0:55 Comment(1)
This worked well for me. Thanks for the example and the link.Keratosis
D
12

Another way to do it, without having to create any additional classes. I've added a set method to show what you do if you only override one of the two:

class Foo(object):
    def _get_age(self):
        return 11

    def _set_age(self, age):
        self._age = age

    age = property(_get_age, _set_age)


class Bar(Foo):
    def _get_age(self):
        return 44

    age = property(_get_age, Foo._set_age)

This is a pretty contrived example, but you should get the idea.

Darees answered 14/11, 2008 at 22:52 Comment(3)
This seems to be a correct solution if python < 2.6 is needed.Goshawk
This is a decent enough solution which works with python 2.4+, but I like the solution I found on geek at play better, as it doesn't require re-creating the property in the subclass -- just the getter or setter method can be overridden, and normal MRO applies.Exfoliation
I use this approach too. I found this code easier to read and subclasses have access to super().value_getter() or setter function.Wyckoff
C
7

Yes, this is the way to do it; the property declaration executes at the time the parent class' definition is executed, which means it can only "see" the versions of the methods which exist on the parent class. So when you redefine one or more of those methods on a child class, you need to re-declare the property using the child class' version of the method(s).

Catenary answered 26/10, 2008 at 3:7 Comment(0)
S
6

A possible workaround might look like:

class Foo:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        print('Foo: getting age')
        return self._age

    @age.setter
    def age(self, value):
        print('Foo: setting age')
        self._age = value


class Bar(Foo):
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        return super().age

    @age.setter
    def age(self, value):
        super(Bar, Bar).age.__set__(self, value)

if __name__ == '__main__':
    f = Foo(11)
    print(f.age)
    b = Bar(44)
    print(b.age)

It prints

Foo: setting age
Foo: getting age
11
Foo: setting age
Foo: getting age
44

Got the idea from "Python Cookbook" by David Beazley & Brian K. Jones. Using Python 3.5.3 on Debian GNU/Linux 9.11 (stretch)

Scantling answered 4/1, 2020 at 19:45 Comment(0)
N
2

Something like this will work

class HackedProperty(object):
    def __init__(self, f):
        self.f = f
    def __get__(self, inst, owner):    
        return getattr(inst, self.f.__name__)()

class Foo(object):
    def _get_age(self):
        return 11
    age = HackedProperty(_get_age)

class Bar(Foo):
    def _get_age(self):
        return 44

print Bar().age
print Foo().age
Nacre answered 26/10, 2008 at 3:17 Comment(2)
for the record a full implementation with set, get and del: infinitesque.net/articles/2005/enhancing Python's property.xhtmlExercise
FWIW, here's a currently working link to the article An Overrideable Alternative to the property Function in Python mentioned in Peter Hoffmann's comment.Absher
I
2

Same as @mr-b's but with decorator.

class Foo(object):
    def _get_meow(self):
        return self._meow + ' from a Foo'
    def _set_meow(self, value):
        self._meow = value
    @property
    def meow(self):
        return self._get_meow()
    @meow.setter
    def meow(self, value):
        self._set_meow(value)

This way, an override can be easily performed:

class Bar(Foo):
    def _get_meow(self):
        return super(Bar, self)._get_meow() + ', altered by a Bar'
Iced answered 20/5, 2016 at 21:11 Comment(0)
T
0

I ran into problems setting a property in a parent class from a child class. The following workround extends a property of a parent but does so by calling the _set_age method of the parent directly. Wrinkled should always be correct. It is a little javathonic though.

import threading


class Foo(object):
    def __init__(self):
        self._age = 0

    def _get_age(self):
        return self._age

    def _set_age(self, age):
        self._age = age

    age = property(_get_age, _set_age)


class ThreadsafeFoo(Foo):

    def __init__(self):
        super(ThreadsafeFoo, self).__init__()
        self.__lock = threading.Lock()
        self.wrinkled = False

    def _get_age(self):
        with self.__lock:
             return super(ThreadsafeFoo, self).age

    def _set_age(self, value):
        with self.__lock:
            self.wrinkled = True if value > 40 else False
            super(ThreadsafeFoo, self)._set_age(value)

    age = property(_get_age, _set_age)
Trefoil answered 2/6, 2015 at 15:52 Comment(0)
M
0

I think the answer from Vladimir Zolotykh is nearly optimal.
For my unterstanding a slightly better variant might be calling the constructer of the superclass.
Resulting in the following code:

class Foo:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        print("Foo: getting age")
        return self._age

    @age.setter
    def age(self, value):
        print("Foo: setting age")
        self._age = value


class Bar(Foo):
    def __init__(self, age):
        super().__init__(age)


if __name__ == "__main__":
    a = Foo(11)
    print(a.age)
    b = Bar(44)
    print(b.age)

With this solution, there is no need to reeimplement the property for the subclass. Just tell the subclass, that it should behave like the superclass by using the constructor.

The upper lines result in the following output

Foo: setting age
Foo: getting age
11
Foo: setting age
Foo: getting age
44

with python3.

Mcmann answered 16/5, 2021 at 16:29 Comment(0)
L
-1
class Foo:
    # Template method
    @property
    def age(self):
        return self.dothis()
    # Hook method of TM is accessor method of property at here
    def dothis(self):
        return 11
class Bar(Foo):
    def dothis(self):
        return 44

Same as Nizam Mohamed, just to mention that style guide 2.13.4 using both template method and property

Legitimatize answered 23/8, 2019 at 3:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.