Python decorators - wrapping a method call in extra code
Archive - Originally posted on "The Horse's Mouth" - 2007-04-15 07:54:44 - Graham EllisWould you like to add an extra set of wrappers around a series of function of method calls - perhaps to add a profiler or logger to your application? These are examples of the things you can do with Python's decorators, introduced (with the syntax I'll show you below) in Python 2.4.
Here's a class of object that I've defined called "User". The only difference from regular code is the @logger line that's been added before each definition to ask for it to be wrapped in (decorated) with something called logger.
class User:
@logger
def __init__(self,username,email):
self.uname = username
self.email = email
@logger
def getemail(self):
return self.email
@logger
def getname(self):
return self.uname
And here's the code of a test application that uses that class:
team = []
team.append(User("Lisa","lisa@wellho.net"))
team.append(User("Leah","leah@wellho.net"))
team.append(User("Christine","christine@wellho.net"))
for member in team:
print "Team member",member.getname(),\
"has email",member.getemail()
The results when I run (with the correct version of Python, of course!) are very much as you would expect:
Dorothy:~/ grahamellis$ /usr/local/bin/python dec01 (Python 2.5)
Team member Lisa has email lisa@wellho.net
Team member Leah has email leah@wellho.net
Team member Christine has email christine@wellho.net
Dorothy:~/ grahamellis$ python dec01 (Python 2.3)
File "dec01", line 21
@logger
^
SyntaxError: invalid syntax
Dorothy:~/ grahamellis$
However ... there's an extra log file that was generated by the code in the decorator -
***__init__ <function __init__ at 0x650f0>
(<__main__.User instance at 0x66620>, 'Lisa', 'lisa@wellho.net'){}
***__init__ <function __init__ at 0x650f0>
(<__main__.User instance at 0x66670>, 'Leah', 'leah@wellho.net'){}
***__init__ <function __init__ at 0x650f0>
(<__main__.User instance at 0x66698>, 'Christine', 'christine@wellho.net'){}
***getname <function getname at 0x651f0>
(<__main__.User instance at 0x66620>,){}
***getemail <function getemail at 0x65170>
(<__main__.User instance at 0x66620>,){}
***getname <function getname at 0x651f0>
(<__main__.User instance at 0x66670>,){}
***getemail <function getemail at 0x65170>
(<__main__.User instance at 0x66670>,){}
***getname <function getname at 0x651f0>
(<__main__.User instance at 0x66698>,){}
***getemail <function getemail at 0x65170>
(<__main__.User instance at 0x66698>,){}
The final piece of the jigsaw is the decorator itself - loaded in the top of the test source file in my example (full source code listing here but more often in its own separate module.
def logger(f, name=None):
# if logger.fhwr isn't defined and open ...
try:
if logger.fhwr:
pass
except:
# ... open it
logger.fhwr = open("log.txt","w")
if name is None:
name = f.func_name
def wrapped(*args, **kwargs):
logger.fhwr.write("***"+name+" "+str(f)+"\n"\
+str(args)+str(kwargs)+"\n\n")
result = f(*args, **kwargs)
return result
wrapped.__doc__ = f.__doc__
return wrapped
That's not a piece of "first day Python", with exceptions to check on the existance of a variable, names containing code blocks, and references to a number of interbal variables - but it will provide you with an excellent springboard from which you can start using decorators. Have you even said to yourself "I wish I could put all my function calls through this extra filter ....? Well - now you can - it's a Python decorator!