Tuesday, March 25, 2008

Python decorators demystified

One of the things you may have noticed if you took a look at some of the newer Python programs is extensive use of decorators. Decorators are relatively new concept in Python world, and they first appeared in version 2.4. Decorators can help you to avoid unnecessary code duplication and to write elegant code in many situations which could otherwise lead to ugly code. Decorators are way to do metaprogramming in Python, ie. to enhance functionality of an existing (decorated) function without modifying it's body.

What precisely is a decorator? Let's first give a more or less formal definition in context of Python programming. Decorator is an object that acts as syntactic glue and is used to modify behavior of a method or a function. You can recognize a decorator by the following syntax:
@<decorator_name>
def <function_name>:
<function_body>
What Python interpreter actually does under the hood is translation of previous code to:
<function_name> = <decorator_name>(<function_name>)
Here decorator acts simply as a wrapper for an existing function, but as we will show later, it can add a new functionality to that function.

Although previous syntax indicates that a function may have only one decorator, that is not true - a function may have more that one decorator. By using the multiple decorators, code is translated from this syntax:
@<decorator1>
@<decorator2>
def <function_name>:
<function_body>
to this:
<function_name> = <decorator1>(<decorator2>(<function_name>))
In all of the examples we have assumed that decorators are already defined, but in your programs you will have to write a decorator before you can use it.

Let's start with the most basic example. We will modify a function that makes a sum of two integers so it will return a result in hexadecimal notation instead of default decimal notation.
def sum(a, b):
return a + b
Instead of modifying the function itself, we write a decorator function. Decorator function takes a function as an argument and it returns a new function which will replace the old one.
def hex(fn):
def new_fn(*args):
return "%x" % fn(*args)
return new_fn
In the body of decorator, we have defined a new function that reads all arguments into a tupple (*args syntax), and then uses a decorated function (fn) to calculate result. The result is then returned in hexadecimal. At the end of decorator body, new function is returned. Now, we can simply modify any function that returns a single integer with this decorator. Just add "@hex" in front of function definition, and it will do it's job.
>>> @hex
... def sum(a, b):
... return a + b
...
>>> sum(10,5)
'f'
Decorators, like everything else in Python, are objects, so we can do more interesting things with them. For example we can pass arguments inside decorator to specify our output more precisely.

What are the possible real world situations where you might use decorators? Let's suppose you're developing a multithreaded application and you need an elegant way to mutually exclude the execution of some functions that run in separate threads. What you want to achieve is basically serialization of two or more functions, i.e. we must prevent starting of other functions when any other is already running. To accomplish this, you can use Python's built in reentrant lock object (threading.RLock) inside of decorator to prevent concurrent execution.and queue object (collections.Queue) to remember what functions (or transactions) must be executed after lock is released.

Good starting point to learn more about decorators is PEP 318.

7 comments:

Anonymous said...

Here's another nice python decorators introduction

ipozgaj said...

yes, that's also a nice article (and the rest of the blog is also interesting)

Anonymous said...

It's mystify, not mistify

rolfst said...

Finally someone who is able to write down in an understandable way what a decorator in python does.
thanx

Panos Laganakos said...

Great post!

Thanks for the insight.

meush said...

after 1 hour of reading about python decorators, your post explains the thing the way i wanted, so that i can be understood in 10 minutes, testing included ! thanks a lot =D

Rob said...

"How to understand python decorators in 10 minutes"
Thank you!

Rob from Italy