Python: A Few Decorator Tips

Here are a few tips for working with Python decorators.

Here's a simple Python decorator, along with some code using it:
def logged(f):

def wrapped(*args, **kargs):
print "I'm calling %s." % f.__name__
try:
return f(*args, **kargs)
finally:
print "Done."

return wrapped

@logged
def sum(a, b):
return a + b

sum(1, 2)
help(sum)
If you run this, you get:
I'm calling sum.
Done.
...
Notice the way I used try, return, and finally. I'm either very cool or very naughty. It's hard to know.

The decorator package can make simple decorators (i.e. ones that don't have any of their own arguments) like this even easier:
from decorator import decorator

@decorator
def logged(f, *args, **kargs):
print "I'm calling %s." % f.__name__
try:
return f(*args, **kargs)
finally:
print "Done."

@logged
def sum(a, b):
return a + b

sum(1, 2)
help(sum)
Notice, I didn't need to write a "wrapped" function.

Furthermore, it preserves the method signature. In the first case, "help(sum)" says "wrapped(*args, **kargs)". In the second case, it says "sum(a, b)", which is better.

As a final tip, what happens if there's a decorator that contains some code, and you want to call it as if it were a normal function? This might happen if you want to call the decorator from the middle of some other function. Here's how:
def sum(a, b):
return a + b

logged(sum)(1, 2)
It makes sense once you think about it.

Comments

Anonymous said…
This is the decorator I use for logging, but it could be improved with the decorator module

def debug_func(self, func):
def result(*args, **kwargs):
print('---%s\ndebugging callable: %s, args: %s, kwargs: %s' % (func.__name__, func, args, kwargs))
result = None
try:
result = func(*args, **kwargs)
finally:
print('the callable %s returned: %s\n---' % (repr(func), repr(result)))
global last_debugged
last_debugged = (func, args, kwargs)
return result
result.__doc__ = func.__doc__
result.__name__ = func.__name__
return result
I mentioned "logged(sum)(1, 2)", but I didn't mention that if you don't actually have a function like sum and you *only* want to call the decorator, you can fake it with:

logged(lambda: None)()
Brandon L. Golm said…
putting the return in the try, and more stuff in the finally is clever, not naughty (though, it makes mortal souls' minds hurt).

Speaking of which, check this out: http://www.hedgerwow.com/360/dhtml/ie6_memory_leak_fix/ (a fix to the IE6 memory leak problem using that pattern).
> Speaking of which, check this out: http://www.hedgerwow.com/360/dhtml/ie6_memory_leak_fix/ (a fix to the IE6 memory leak problem using that pattern).

Very nice ;)