Python decorator mini-study (part 2 of 3)
Introduction
In the previous part of this article I introduced the Params2attribs decorator that copies object initialiser parameters to object attributes.
In this part I aim to introduce the Python decorator mechanisms needed to drill down into Params2attribs in the forthcoming third part of this series.
Python decorator 101
Python decorators come in two flavours: simple and not so simple
The Params2attribs decorator which is the subject of this article series falls into the second category. But, as they say, first things first..
Simple decorators
The simple decorator variety is in essence a function that returns a wrapper function that is to be invoked instead of the wrapped function. Confused? You should be
Let’s have a look at an example. In the code below the wrapper function (lines 13 - 16) merely logs the invocation of the wrapped function.
Just in case you haven’t seen this before: the *args and **kwargs constructs (on lines 13 and 15) are the Python way to pass positional and keyword function parameters in a generic fashion.
1 #!/usr/bin/env python 2 """simple decorator example""" 3 4 # Copyright: (c) Muharem Hrnjadovic 5 # created: 17/10/2006 08:25:09 6 7 __version__ = "$Id$" 8 # $HeadURL $ 9 10 from sys import stdout 11 12 def interceptor(f): 13 def wrapper(*args, **kwargs): 14 stdout.write("Hey %s(), you have been wrapped..\\n\\t" % f.__name__) 15 f(*args, **kwargs) 16 stdout.write(".. resistance is futile\\n") 17 return wrapper 18 19 @interceptor 20 def plf(): stdout.write("Help! I am only a poor little function!\\n") 21 22 if __name__ == ‘__main__‘: 23 plf()
A few points to note here: the decorator function interceptor()
- is invoked by Python whenever it loads any code decorated with it (lines 19 - 20)
- defines (lines 13 - 16) and returns (line 17) another function (
wrapper()) that is to be invoked (line 23) instead of the decorated one (plf())
As you may have guessed already, running the code above results in the following output:
mhr@playground2:~/src/published$ python simpledeco.py
Hey plf(), you have been wrapped..
Help! I am only a poor little function!
.. resistance is futile
Complex decorators
Complex decorators may be implemented as classes or using nested functions. The latter variant was suggested by a reader and is shown after the section on decorator classes.
Decorator classes
When using decorator classes any data beyond the function to be wrapped is passed to the decorator class initialiser (see e.g. the before and after parameters of initialiser interceptor::__init__(), lines 13 - 15).
Furthermore, instances of decorator classes must be callable like functions. Our decorator class is hence implementing the special method __call__ (lines 16 - 21).
1 #!/usr/bin/env python 2 """not so simple decorator example""" 3 4 # Copyright: (c) Muharem Hrnjadovic 5 # created: 17/10/2006 08:25:09 6 7 __version__ = "$Id$" 8 # $HeadURL $ 9 10 from sys import stdout 11 12 class interceptor(object): 13 def __init__(self, before, after): 14 self.before = before 15 self.after = after 16 def __call__(self, f): 17 def wrapper(*args, **kwargs): 18 stdout.write(self.before) 19 f(*args, **kwargs) 20 stdout.write(self.after) 21 return wrapper 22 23 @interceptor(‘>> ‘, ‘ <<\\n‘) 24 def plf(): stdout.write("Help! I am only a poor little function!") 25 26 if __name__ == ‘__main__‘: 27 plf()
The code above works approximately in the following fashion:
- Python loads the module and encounters the decorated code on lines 23 - 24
- at that point it will instantiate an
interceptorobject and pass the two string parameters to its initialiser - the initialiser just copies the
beforeandafterparameters to object attributes for later use (lines 13 - 15) - then the
interceptorobject (instantiated in the previous step) is called by Python as a function at which point its__call__method is invoked __call__defines (lines 17 - 20) and returns (line 21) the wrapper function- the call to
plf()(line 27) invokes thewrapper()function defined and returned in step 5
The output (did you guess it?) is as follows:
mhr@playground2:~/src/published$ python notsosimpledeco.py >> Help! I am only a poor little function! <<
Nested functions
As a reader pointed out after the first draft of this article was posted (see also the first two comments on this weblog entry) a similar effect can be achieved using nested functions. The nested functions variant of the decorator looks as follows:
1 #!/usr/bin/env python 2 """a variant of the not so simple decorator example""" 3 4 from sys import stdout 5 6 def interceptor(before, after): 7 def interceptor_inner(f): 8 def wrapper(*args, **kwargs): 9 stdout.write(before) 10 f(*args, **kwargs) 11 stdout.write(after) 12 return wrapper 13 return interceptor_inner 14 15 @interceptor(‘>> ‘, ‘ <<‘\\n‘) 16 def plf(): stdout.write("Help! I am only a poor little function!") 17 18 if __name__ == ‘__main__‘: 19 plf()
Conclusion
This is the end of part two of the Python decorator mini-study. In the next part of this article (part 3) we’ll finally get to dissect the Params2attribs decorator introduced in part one
October 20, 2006 at 12:39 pm
>Complex decorators have to be implemented as classes.
This is not true - you can implement your interceptor decorator using just functions as below:
def interceptor(before, after):
def decorator(f):
def wrapper(*args, **kwargs):
stdout.write(before)
f(*args, **kwargs)
stdout.write(after)
return wrapper
return decorator
What is actually happening is that _calling_ the interceptor function is returning the decorator to use, which is then called as per the normal decorator application. The same thing as is happening with the class version, where calling creates and returns an instance, which is then used as the decorator, and called.
October 20, 2006 at 12:48 pm
My indentation got mangled there. The corresponding decorator is:
print “”"
def interceptor(before, after):
| def decorator(f):
| | def wrapper(*args, **kwargs):
| | | stdout.write(before)
| | | f(*args, **kwargs)
| | | stdout.write(after)
| | return wrapper
| return decorator
“”".replace(”| “,” “
October 20, 2006 at 1:45 pm
Hello Brian,
thank you for pointing this out! I’ll amend part2 to include your correction.
October 14, 2007 at 10:55 am
[...] an individual message which contains information about how to avoid the deprecated method. I found this parameterized decorator example and customized it for my [...]
March 28, 2008 at 1:12 am
[...] Non-Typing news, this post (and this one) on Python Decorators is quite good, although more recently I saw some code that looked like [...]
June 23, 2008 at 7:27 am
Thanks dude, great article on python decorators. There seems to be no mention of python decorators in Dive Into Python book, nor the python tutorial on python.org.