Pretty Decorators
Python decorators are cool, but they can become very messy if you sart taking arguments:
def decorate_with_args(arg):
def the_decorator(func):
def run_it():
func(arg)
return run_it
return the_decorator
@decorate_with_args('some string')
def messy(s):
print s
ugh. Three levels of function definitions for a single decorator. And heaven forbid you want the decorator to be useable without supplying any arguments (not even empty brackets).
So then, I present a much cleaner decorator helper class:
class ParamDecorator(object):
def __init__(self, decorator_function):
self.func = decorator_function
def __call__(self, *args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# we're being called without paramaters
# (just the decorated function)
return self.func(args[0])
return self.decorate_with(args, kwargs)
def decorate_with(self, args, kwargs):
def decorator(callable_):
return self.func(callable_, *args, **kwargs)
return decorator
All that’s required of you is to take the decorated function as your first argument, and then any additional (optional) arguments. For example, here’s how you might implement a “pending” and “trace” decorator:
@ParamDecorator
def pending(decorated, reason='no reason given'):
def run(*args, **kwargs):
print "function '%s' is pending (%s)" % (decorated.__name__, reason)
return run
@ParamDecorator
def trace(decorated, label=None):
if label is None:
label = decorated.__name__
else:
label = "%s (%s)" % (decorated.__name__, label)
def run(*args, **kwargs):
print "%s: started (args=%s, kwargs=%s)" % (label, args, kwargs)
ret = decorated(*args, **kwargs)
print "%s: returning: %s" % (label, ret)
return ret
return run
Which can then be used as either standard or paramaterised decorators:
@pending
def a():
pass
@pending("I haven't done it yet!")
def b():
pass
@trace
def foo():
return "blah"
@trace("important function")
def bar():
return "blech!"
And just to show what this all amounts to:
if __name__ == '__main__':
a()
b()
foo()
bar()
reveals the following output:
function 'a' is pending (no reason given)
function 'b' is pending (I haven't done it yet!)
foo: started (args=(), kwargs={})
foo: returning: blah
bar (important function): started (args=(), kwargs={})
bar (important function): returning: blech!
Fairly simple stuff, but hopefully useful for anyone who finds themselves tripped up by decorators - particularly when trying to allow for both naked and paramaterised decorators.
P.S: I’ve made a pastie of the code in this post, because my weblog engine is not cool enough to colour-code python ;)