Recently, I was tracing the source code of PyPy, and a special decorator named
@property
caught my attention. It seems to be a mechanism for Python
programmers to create a getter, a setter, and a deleter for an instance variable.
For example, how could we intercept the access to a.f
in the following
code snippet?
a = Temperature(5)
print(a.f, 'F')
a.f = 122
print(a.f, 'F')
Here are the instructions:
- Define a
Temperature
class. - Decorate the getter function
f()
(the first one) with the@property
decorator. - If you would like to intercept the setter, decorate the setter
function
f()
(the second one) with the@f.setter
decorator. - If you would like to intercept the deleter, decorate the deleter
function
f()
(the third one) with the@f.deleter
decorator.
This is the code snippet for our Temperature
class:
class Temperature(object):
def __init__(self, c=0):
self.c = c
@property
def f(self):
print('Gets:', self.c, 'C')
return self.c * 9 / 5 + 32
@f.setter
def f(self, val):
print('Sets:', val, 'F / Prev:', self.c, 'C')
self.c = (val - 32) * 5 / 9
@f.deleter
def f(self):
print('Dels:', self.c, 'C')
self.c = 0
Now, we can test the Temperature
class with:
a = Temperature(5)
print(a.f, 'F')
a.f = 122
print(a.f, 'F')
del a.f
Here's the output:
Gets: 5 C
41.0 F
Sets: 122 F / Prev: 5 C
Gets: 50.0 C
122.0 F
Dels: 50.0 C
Enhancement
Notice that three property
objects will be created by the decorators.
We can avoid two of them by initializing the property
object at once:
class Temperature(object):
def __init__(self, c=0):
self.c = c
def _get_f(self):
print('Gets:', self.c, 'C')
return self.c * 9 / 5 + 32
def _set_f(self, val):
print('Sets:', val, 'F / Prev:', self.c, 'C')
self.c = (val - 32) * 5 / 9
def _del_f(self):
print('Dels:', self.c, 'C')
self.c = 0
f = property(_get_f, _set_f, _del_f)
This version is functionally-equivalent to the one shown above.
Remarks
The decorator syntax in the Python programming language is a syntax sugar. Thus, the following code:
class A(object):
@decorator
def func():
pass
is equivalent to:
class A(object):
def func():
pass
func = decorator(func)
In addition, a decorator function is a function that takes a function and returns a decorated function.
The @property
decorator, which we are talking about in this post, is in
fact a built-in class which takes 0-4 actual arguments. Furthermore,
every instance of property
class contains 3 instance methods:
getter
, setter
, and deleter
which will return new
property
instances with the updated accessors. You can find a
pure Python implementation from the Descriptor HowTo Guide.