Python Property Decorator

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:

  1. Define a Temperature class.
  2. Decorate the getter function f() (the first one) with the @property decorator.
  3. If you would like to intercept the setter, decorate the setter function f() (the second one) with the @f.setter decorator.
  4. 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.