Well, to simplify it, let's just deal with functions, first. Let's say you have a function that prints something about its arguments:
def print_info(breed, name):
print "The doggie %s's breed is %s." % (name, breed)
As such:
>>> print_info("Labrador", "Spike")
The doggie Spike's breed is Labrador.
>>> print_info("Pit Bull", "Spot")
The doggie Spot's breed is Pit Bull.
Now you want that function instead to always lowercase the breed. So in a sane way you'd just do this:
def manually_lowered_print_info(breed, name):
print "The doggie %s's breed is %s." % (name, breed.lower())
Output is:
>>> manually_lowered_print_info("Labrador", "Spike")
The doggie Spike's breed is labrador.
But let's say for some reason you very often had to lowercase the first string argument of functions you write so you wanted to abstract this away as a decorator. We want it to look like this and have the same output:
@lower_first_arg
def dec_lowered_print_info(breed, name):
print "The doggie %s's breed is %s." % (name, breed)
This is just syntax sugar for this:
def tmp_func(breed, name):
print "The doggie %s's breed is %s." % (name, breed)
dec_lowered_print_info = lower_first_arg(tmp_func)
So we want lower_first_arg
to return the transformed print_info
function. Let's first make a function specifically tailored for the print_info
function.
def lower_first_arg(print_info_func_arg):
def inner_print_info(breed, name):
return print_info_func_arg(breed.lower(), name)
return inner_print_info
Does it work? Let's see:
>>> transformed_print_info = lower_first_arg(print_info)
>>> print_info("Pit Bull", "Spot")
The doggie Spot's breed is Pit Bull.
>>> transformed_print_info("Pit Bull", "Spot")
The doggie Spot's breed is pit bull.
Great! Notice that we are passing print_info
as an argument to the lower_first_arg
function, where it gets referred to by the local variable print_info_func_arg
.
If we use the decorator syntax it works identically:
@lower_first_arg
def dec_lowered_print_info(breed, name):
print "The doggie %s's breed is %s." % (name, breed)
Outstanding:
>>> dec_lowered_print_info("Pit Bull", "Spot")
The doggie Spot's breed is pit bull.
Cool, so that's it, really. Now to make the decorator more generic, let's first generalize the names:
def generic_lower_first_arg(f):
def wrapped(arg1, arg2):
return f(arg1.lower(), arg2)
return wrapped
Now the problem here is this decorator only works on functions of 2 args. Ideally we'd want it to work on any function of 1 arg or more, e.g.:
@generic_lower_first_arg
def failed_whatnow(first, last, middle):
print "%s %s %s" % (first, middle, last)
Now, that code will run, but we get an error if we try to call it:
>>> failed_whatnow("Bob", "Jones", "The Killer")
Traceback (most recent call last):
File "<pyshell#26>", line 1, in <module>
failed_whatnow("Bob", "Jones", "The Killer")
TypeError: wrapped() takes exactly 2 arguments (3 given)
What's going on? Well, the decorator took failed_whatnow
and returned the function it defined, wrapped
, but that wrapped
function only takes two arguments! The fix here is to use the varargs syntax. It's also generally a good idea to pass along any keyword args that might be given to the wrapped function.
def proper_lower_first_arg(f):
def wrapped(arg1, *args, **kwargs):
return f(arg1.lower(), *args, **kwargs)
return wrapped
And now it works on all sorts of functions:
@proper_lower_first_arg
def proper_whatnow(first, last, middle):
print "%s %s %s" % (first, middle, last)
@proper_lower_first_arg
def multiplyit(mm, n=3):
return mm * n
Proof:
>>> proper_whatnow("Bob", "Jones", "The Killer")
bob The Killer Jones
>>> multiplyit("HaHa.Fool!")
'haha.fool!haha.fool!haha.fool!'
>>> multiplyit("HaHa.Fool!", n=5)
'haha.fool!haha.fool!haha.fool!haha.fool!haha.fool!'