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.
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:
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“!