The magic of python decorators
Posted by Jason Baker on April 25, 2009
Decorators in Python are one of the language’s more “magical” features. Personally, I’ve tended to glaze over decorators in code because they always seemed to be fairly self-explanatory. But how do you make your own? Personally, I think understanding the uses of decorators and being able to write your own is one of the points where a python newbie transitions to a knowledgeable pythonista.
But what is a decorator?
In actuality, there’s not an actual language construct to define decorators. Any function that takes a function as a parameter and returns one as a result may be used as a decorator. These are known as “higher-order functions” in functional programming circles.
Chances are, you’ve already seen decorators in use and maybe even used them. I’ll give you a common example of decorator usage:
class SomeClass(object): @property def x(self): return 5 >>> var = SomeClass() >>> var.x 5
For those of you familiar with the concept of properties, it should be pretty straight-forward what's going on here. But where the heck did property come from? I'll give you a hint. The above code works identically to this code:
class SomeClass(object): def x(self): return 5 x = property(x)
I can write my own?!
Yes you can. A lot of well-written libraries make very good use of decorators. And given the right situation, the little bit of syntactic sugar they provide can do a lot of good. But decorators aren't just there for others to define. Once you wrap your head around decorators, they can save you a lot of copy-and-pasting (which you're not doing anyway, right?) when used in your own code. To illustrate this, I want to show you a couple of very much real-world cases that I've found decorators to be useful.
Those pesky connection objects
I have a library that needs to call a few particular stored procedures in a SQL Server database. Because my ORM doesn't support stored procedures, I have to use straight adodbapi. The calls look something like this:
import adodbapi def LookupPerson(): conn = adodbapi.connect(CONNECTION_STRING) try: #do stuff here finally: conn.close()
This is all well and good for just one function. But what happens when you need 4 or 5 of these? And what about the visual cruft that the try finally block is adding to the function (adodbapi doesn't support with blocks before you ask)? I'm sure you've already guessed the solution by now. Here's how you can solve this problem:
import adodbapi def with_connection(func): def _exec(*args, **argd): conn = adodbapi.connect(CONNECTION_STRING) try: func(conn, *args, **argd) finally: conn.close return _exec @with_connection def LookupPerson(conn): #do stuff here LookupPerson() #conn argument is passed by the decorator
I think that the simplification that happens with LookupPerson here should be obvious. But what is the purpose of the *args and **argd shenanigans? The documentation covers arbitrary argument lists in depth, so I won't go into too much detail. But what happens if I want to lookup a person by name? The LookupPerson function would be transformed to this:
@with_connection def LookupPerson(conn, name): #do stuff here LookupPerson('Bob') #conn argument is passed by the decorator LookupPerson(name='Jill')
In fact, I can decorate any function that takes any number of arguments either by keyword or by position. Pretty neat, eh?
When doing web applications, it's pretty important to have decent error handling. But setting this up can be a pain. For instance, what if I wanted to make my django application print a wonderfully informative traceback to a log file? I could do that like this:
from traceback import format_exc def index(request): try: #do stuff except: logging.log(format_exc()) return HttpResponseServerError('Error!')
But this definitely can become problematic. What happens if you duplicate this code in all of your view functions and you want to make a change to your error handling? The solution is simple:
def handle_errors(func): def _handler(*args, **argd): try: func(*args, **argd) except: logging.log(format_exc()) return HttpResponseServerError('Error!') @handle_errors def index(request): #do stuff here
As you can tell, this allows us to make our views worry about actually doing stuff instead of constantly handling errors. Yes, there are also middlewares for doing this kind of thing. But then I wouldn't have a reason to make a blog post about python decorators, would I?
Ok, so I'll admit something. Python's decorator syntax is ugly. Its Java-like syntax alone may even be enough to scare some off. But as I've show, there are at least a few real-world cases where they are useful.
What are some other decorators that you've found to be useful?