You can read about Python Decorators in the PEP like I had to, or you can just skip to the good stuff.
Python decorators are a way of modifying a function, of quickly wrapping it. They’re sort of like aspect-oriented programming, in that they let you define logic that cross-cuts methods all over your program. Here’s an example that takes a method that returns a list, and converts that list to a CSV file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import csv import StringIO def list2csv(func): def wrapper(*args, **kwargs): listValue = func(*args, **kwargs) fd = StringIO.StringIO() writer = csv.writer(fd) for row in listValue: writer.writerow(row) return fd.getvalue() return wrapper @list2csv def getList(): return [ ('First Name', 'Last Name', 'Profession'), ('Ken', 'Kinder', 'Software Engineer'), ('John', 'Stewart', 'Funny man'), ('Guido', 'Van Russum', 'Smart guy') ] print getList() |
Results in…
First Name,Last Name,Profession Ken,Kinder,Software Engineer John,Stewart,Funny man Guido,Van Russum,Smart guy
Now, you may have seen a few decorators that take keyword arguments, and look like this:
1 2 3 4 5 6 7 8 | @list2csv(dialect=csv.exel) def getList(): return [ ('First Name', 'Last Name', 'Profession'), ('Ken', 'Kinder', 'Software Engineer'), ('John', 'Stewart', 'Funny man'), ('Guido', 'Van Russum', 'Smart guy') ] |
If you’re like me, you’re tempted to just add a keyword argument, dialect, to the list2csv method. But what’s important to keep in mind is that the text after the @ in your decoration is evaluated to being the decorator. In other words, @list2csv is not a shortcut to @list2csv(). If list2csv is called, it should return a decorator itself. Here’s how:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import csv import StringIO class Tabs(csv.excel): delimiter = '\t' def list2csv(func=None, dialect='excel'): if func is None: return lambda _: list2csv(_, dialect) def wrapper(*args, **kwargs): listValue = func(*args, **kwargs) fd = StringIO.StringIO() writer = csv.writer(fd, dialect) for row in listValue: writer.writerow(row) return fd.getvalue() return wrapper @list2csv(dialect=Tabs) def getList(): return [ ('First Name', 'Last Name', 'Profession'), ('Ken', 'Kinder', 'Software Engineer'), ('John', 'Stewart', 'Funny man'), ('Guido', 'Van Russum', 'Smart guy') ] print getList() |
This is a trick that lets you pass keyword arguments to the decorator. Notice lines 8 and 9, then 20. When this method is the called with a function, it behaves as normal, otherwise it returns a lambda to itself, with the arguments already specified.
Insofar as I don’t know what this means, I am inclined to make a bad joke.
Instead I think I will go take a nap. Hi, Ken!