Interesting decorator use.

Steven Bethard steven.bethard at gmail.com
Thu Feb 24 17:00:46 EST 2005


Tom Willis wrote:
> Question on decorators in general. Can you parameterize those?
> 
> If I wanted to something and after the function call for example, I
> would expect something like this would work.
> 
> def prepostdecorator(function,pre,post):
>     def wrapper(*args,**kwargs):
>         pre()
>         result =  function(*args,**kwargs)
>         post()
>         return result
>     return wrapper
> 
> def dopre():
>     print "call pre"
> 
> def dopost():
>     print "call post"
> 
> @prepostdecorator(pre,post)
> def sayhello(Name):
>     print "Hey %s, nice to meet you" % Name
> 
> #sayhello = prepostdecorator(sayhello,dopre,dopost)
> 
> if __name__=="__main__":
>     sayhello("Dude")
> 
> but I get ...
> TypeError: prepostdecorator() takes exactly 3 arguments (2 given)

You get this TypeError for the same reason that I get the following 
TypeError:

py> def prepostdecorator(function, pre, post):
...     pass
...
py> prepostdecorator(1, 2)
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
TypeError: prepostdecorator() takes exactly 3 arguments (2 given)

The expression after @ is a _normal Python expression_.  So since you 
couldn't call prepostdecorator with 2 arguments in any other situation, 
you still can't.

If you want to call prepostdecorator with 2 arguments, you need to write 
it this way.  A few options:

(1) Use nested functions:

py> def prepostdecorator(pre,post):
...     def decorator(function):
...         def wrapper(*args,**kwargs):
...             pre()
...             result =  function(*args,**kwargs)
...             post()
...             return result
...         return wrapper
...     return decorator
...
py> @prepostdecorator(dopre, dopost)
... def sayhello(name):
...     print "Hey %s, nice to meet you" % name
...
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

(2) Use functional.partial (PEP 309[1])

py> def prepostdecorator(pre, post, function):
...     def wrapper(*args,**kwargs):
...         pre()
...         result =  function(*args,**kwargs)
...         post()
...         return result
...     return wrapper
...
py> @partial(prepostdecorator, dopre, dopost)
... def sayhello(name):
...     print "Hey %s, nice to meet you" % name
...
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

(3) Use a class:

py> class prepostdecorator(object):
...     def __init__(self, pre, post):
...         self.pre, self.post = pre, post
...     def __call__(self, function):
...         def wrapper(*args,**kwargs):
...             self.pre()
...             result = self.function(*args,**kwargs)
...             self.post()
...             return result
...         return wrapper
py> @prepostdecorator(dopre, dopost)
... def sayhello(name):
...     print "Hey %s, nice to meet you" % name
...
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

Note that in all of the above cases, the result of evaluating the 
expression after the @ is a callable object that takes _exactly one_ 
argument, the function to be decorated.

HTH,

STeVe

[1] http://www.python.org/peps/pep-0309.html



More information about the Python-list mailing list