About getters and setters in Python

Recently I’ve been developing a fairly large Python application, involving a somewhat articulate and deep class hierarchy. With time, I ended up writing a certain amount of getters and setters which felt suspiciously unpythonic. Looking for better solutions I stumbled upon a number of sources making fun of people writing getters and setters in Python (e.g. Python Is Not Java), because:

  • there’s no real data member protection in Python anyway (“we are all consenting adults here”)
  • if you later want to embed some logic in the retrieval of an attribute, you can keep the same interface by just adding a property

These arguments were so compelling that made me feel pretty stupid. Consequently, I immediately started converting all my getters and setters into variables or, when needed, into properties.

Unfortunately, it soon became apparent that I had not considered all the facets of the problem. Problems surfaced soon, primarily because, as I had to find out the hard way, avoiding getters/setters does not play that well with inheritance.

First of all, overriding the access of an attribute becomes a bit quirky — despite being perfectly acceptable when using getters. In particular, the child class must know if a certain attribute is indeed an attribute or a property — and this already breaks the promise of keeping the switch from variable to property a local change.

1
2
3
4
5
6
7
8
9
10
11
12
class Foo(object):
    @property
    def foo(self):
        return 5

class Bar(Foo):
    @property
    def foo(self):
        return Foo.foo.fget(self) + 1

print Foo().foo # 5
print Bar().foo # 6

The syntax gets really cumbersome. For instance, if you want to override only the getter or the setter, verbosity ensues:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Foo(object):
    @property
    def foo(self):
        return 5

    @foo.setter
    def foo(self, val):
        print val

# one way to do that
class Bar1(Foo):
    @property
    def foo(self):
        return Foo.foo.fget(self) + 1

    @foo.setter
    def foo(self, val):
        return Foo.foo.fset(self, val)

# another way
class Bar2(Foo):
    def foo_get(self):
        return Foo.foo.fget(self) + 1

    def foo_set(self, val):
        return Foo.foo.fset(self, val)

    foo = property(foo_get, foo_set)

Foo().foo = 4
Bar1().foo = 3
Bar2().foo = 2

Granted, true Pythoners would say that this is not Python’s fault but mine, as I have evidently made some wrong, or at least unpythonic, design decisions at a higher level. That’s an acceptable criticism, but I still think that the organization of my program was fair when employing getters and setters. Most importantly, though, the consequences of substituting them in class hierarchies where overriding is common have not been sufficiently explained by the previously mentioned Internet sources.

In other words, sometimes “practicality beats purity“!

  1. If the properties aren’t performance-critical, you can always make the property in the base class be property(lambda s:s.get_foo(), lambda s,v: s.set_foo(v)). Then, simply override the getter or setter in the subclass(es) involved.

    In any case, there are plenty of worse ways that changes to a base class can impact a subclass; subclassing is inherently a tighter coupling between classes than mere use.

  2. @PJ Eby
    Interesting solution, it didn’t occur to me… it could indeed solve my problems.

    (Thanks for commenting, I really appreciated it.)

    • Sean
    • March 11th, 2011

    In your first example, you could use super():
    class Bar(Foo):
    @property
    def foo(self):
    return super( Bar, self).foo + 1

    this way you don’t need know if the attribute is a property instance

    And in your second example, knowing it is a property, you can override the getter with:
    class Bar(Foo):
    @Foo.foo.getter
    def foo( self ):
    return super( Bar, self).foo + 1

    This works for me in py2.6.5, obviously your article was from some time ago, I am not sure when exactly this became effective.

    • Erik Bray
    • September 28th, 2011

    I recently encountered this problem–not for the first time in general, but the first time in which setters are involved.

    Sean mentioned above that you can user super() to access the the property through the base class:

    super(Bar, self).foo

    This has always worked for me. But interestingly, if I try to override a setter and I do:

    @foo.setter
    def foo(self, value):
    super(Bar, self).foo = value

    It explodes with an AttributeError, claiming that the super object has no attribute ‘foo’. I guess this has something to do with super.__setattr__, but I’m not sure. I’d consider this a bug though. Either one should work or neither should work.

    • Erik Bray
    • September 28th, 2011

    Though it seems this quirk is actually a “feature”. See: http://bugs.python.org/issue505028

    I guess the answer is to not use super, and instead use the kludgy Foo.foo.fget/Foo.foo.fset.

    It’s claimed that data descriptors shouldn’t be overridden by a subclass, but I can’t see what the justification is for that proclamation. There are perfectly good reasons to do this.

  3. @Sean
    Thanks for pointing that out, although I think my main point still stands: for class hierarchies, proper getters and setters are just syntactically cleaner and easier to understand.

    @Erik Bray
    Ah that’s interesting, thanks for sharing! And again, it’s an additional reason not to fiddle with properties in a class hierarchy if not necessary.

    In the end I think class hierarchies are falling out of fashion, and in Python this is particularly true because duck typing is more general than inheritance-based polymorphism… So I guess this is why some mechanisms are not entirely orthogonal with respect to inheritance – in other words nobody cares. That’s fine by me but then again I just wish Pythoners were not so smug in dismissing the use of getters and setters, as there are clearly cases for those…

  1. No trackbacks yet.