Thursday, September 22, 2011

Python Decorators

So it came to my attention yesterday that the use of decorators in python is not extremely well known. A couple years ago I had discovered them and have been using them whenever I can. Decorators are one of those things that you don’t realize you need until you know learn about it.
So what exactly is a decorator?

From the python wiki:
A decorator is the name used for a software design pattern. Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated.

To put it simply though, a decorator allows you to execute code before and after a function is called, effectively wrapping the function around other code, dynamically.

For a more practical example, lets using a simple, common task that a python developer would use, timing a function. Without the use of decorators there are multiple ways you can do this, most involve duplicating code. If you want to time a lot of functions, you could be potentially duplicating many lines of code and altering your original source code. Adding and removing the timing functionality could be a very tedious task at the end of the day.

This is what I see timing code most commonly look like in developers who do not know about decorators:

import time
def func_a(*args):
start_time = time.time()
end_time = time.time()
print end_time-start_time

Now while the duplication of code isn’t too horrible, imagine if you have hundreds of functions and want to time all of them, you’d have to add that code to all functions you want to time. Quite a daunting task on a large script.
How do I use decorators?

Setup for decorators is actually really simple. Setup consists of two parts, the decorator function and adding the decorator to the function.
Decorator Function

Keeping with the timing example above, the function for a decorator is quite simple. The decorator is a function that takes a function as an argument. Inside of that function is another function that does the wrapping. Using the timing example, this is what a decorator function would look like:

def print_timing(func):
def wrapper(*arg, **kwds):
# Start the time
t1 = time.time()

# Run the function with the same arguments passed in to the original function
res = func(*arg, **kwds)

# Stop the time
t2 = time.time()

# Tell me how long it took
print '%s took %0.3f s' % (func.func_name, (t2-t1))

return res
return wrapper

Now that we have our decorator function, we simply need to decorate the functions which we want to wrap. The python phrase for this is @. In our example it would be @print_time. To decorate the function we place @print_timing on the line directly above the def of the function we want to time. For example:

def func_a(*args):

So there you have it, you can now decorate all the functions you want. No matter where the function is called from, python will dynamicaly alter the execution and run print_timing instead.

The issue of decorators was actually brought up to resolve an issue with OpenGL effecting the redraw of WX elements that were being dynamically updated. A decorator function was used to solve the issues, when I had mentioned the solution to a collegue of mine, he had never heard of decorators before, which brought up this whole post.

No comments:

Post a Comment