Main Content

Python decorators - wrapping a method call in extra code

Archive - Originally posted on "The Horse's Mouth" - 2007-04-15 07:54:44 - Graham Ellis

Would 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!