Pythonic code: the property decorator
Matt Layman
Posted on March 25, 2020
This post continues a series on "Pythonic" code. Pythonic code is code that fits well with the design of the Python language. My previous post looked at the with statement and simplifying setup and tear down code. This third post will examine the property
decorator.
The property
decorator
If you've ever taken a class on object oriented (OO) design, then you've likely been taught the value of encapsulation. You were told that you should keep the state of your objects private so that you're free to modify the class internals as you see fit. This is a noble goal and it benefits you immensely.
If the course was in Java, encapsulation quickly translated itself to getters and setters on your classes. When coming to Python, maybe your first class looks like this:
class OhMyJava:
def __init__(self):
self._foo = ''
def get_foo(self):
return self._foo
def set_foo(self, foo):
self._foo = foo
If I were to review your code, I might propose an alternative.
class AwwYeah:
def __init__(self):
self.foo = ''
WHAT!? But, how dare I violate encapsulation? What happens when needs change, and foo
needs to be created dynamically from bar
. Doesn't that break anyone who used the code?
The Python language designers determined that getting and setting attributes directly feels far cleaner than getter and setter methods. Contrast these:
obj.set_result(other.get_foo() + other.get_bar())
# vs.
obj.result = other.foo + other.bar
I hope you'll agree with the language designers that the latter is easier to comprehend. To make that style possible, the language needed some way to achieve what they wanted without violating encapsulation.
Enter the property
decorator.
The genius move was to add a new language feature that makes it possible to extend your class without violating the API that you've defined. In our example, foo
is the public API. Let's extend our code.
class AwwYeah:
def __init__(self):
self._bar = ''
@property
def foo(self):
return 'More awesome please: {}'.format(self._bar)
@foo.setter
def foo(self, value):
self._bar = '{} is great.'.format(value)
>>> a = AwwYeah()
>>> a.foo = 'Python'
>>> a.foo
'More awesome please: Python is great.'
The property
decorator is our secret weapon to prevent encapulation blunders. This new class changes what foo
means without breaking any users of the class.
Using property
as a decorator creates a getter method. It's the difference between a.foo()
and a.foo
. That seems minor, but those parenthesis add up. The property
decorator enables a method to masquerade as a plain object attribute.
Additionally, you can create a setter method (i.e. @foo.setter
) as long as the name before .setter
matches the method name of the decorated property. When you don't include a setter method, the property is read only and will raise an AttributeError
if you attempt to set it.
Creating getter and setter methods by adding a decorator is a powerful tool that leads to exceptionally clean code. The property
decorator unlocks something awesome for us:
You can do the simplest thing possible until you need more control.
This characteristic allows developers to write code quickly and succinctly. If you want to know more about property
, check out the documentation. So, go forth and stop writing Java-style getter and setter methods in your Python code! :)
This article first appeared on mattlayman.com.
Posted on March 25, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.