程式扎記: [ Python 文章收集 ] Python Decorators II: Decorator Arguments

標籤

2015年4月9日 星期四

[ Python 文章收集 ] Python Decorators II: Decorator Arguments

Source From Here 
Review: Decorators without Arguments 
In part I, I showed how to use decorators without arguments, primarily using classes as decorators because I find them easier to think about. 

If we create a decorator without arguments, the function to be decorated is passed to the constructor, and the __call__() method is called whenever the decorated function is invoked: 
  1. # -*- coding: utf-8 -*-  
  2. class decoratorWithoutArguments(object):  
  3.   
  4.     def __init__(self, f):  
  5.         """  
  6.         If there are no decorator arguments, the function  
  7.         to be decorated is passed to the constructor.  
  8.         """  
  9.         print "Inside __init__()"  
  10.         self.f = f  
  11.   
  12.     def __call__(self, *args):  
  13.         """  
  14.         The __call__ method is not called until the  
  15.         decorated function is called.  
  16.         """  
  17.         print "Inside __call__()"  
  18.         self.f(*args)  
  19.         print "After self.f(*args)"  
  20.   
  21. @decoratorWithoutArguments  
  22. def sayHello(a1, a2, a3, a4):  
  23.     print 'sayHello arguments:', a1, a2, a3, a4  
  24.   
  25. print "After decoration"  
  26.   
  27. print "Preparing to call sayHello()"  
  28. sayHello("say""hello""argument""list")  
  29. print "After first sayHello() call"  
  30. sayHello("a""different""set of""arguments")  
  31. print "After second sayHello() call"  
Any arguments for the decorated function are just passed to __call__(). The output is: 
Inside __init__()
After decoration
Preparing to call sayHello()
Inside __call__()
sayHello arguments: say hello argument list
After self.f(*args)
After first sayHello() call
Inside __call__()
sayHello arguments: a different set of arguments
After self.f(*args)
After second sayHello() call

Notice that __init__() is the only method called to perform decoration, and __call__() is called every time you call the decorated sayHello()

Decorators with Arguments 
Now let's modify the above example to see what happens when we add arguments to the decorator: 
  1. class decoratorWithArguments(object):  
  2.   
  3.     def __init__(self, arg1, arg2, arg3):  
  4.         """  
  5.         If there are decorator arguments, the function  
  6.         to be decorated is not passed to the constructor!  
  7.         """  
  8.         print "Inside __init__()"  
  9.         self.arg1 = arg1  
  10.         self.arg2 = arg2  
  11.         self.arg3 = arg3  
  12.   
  13.     def __call__(self, f):  
  14.         """  
  15.         If there are decorator arguments, __call__() is only called  
  16.         once, as part of the decoration process! You can only give  
  17.         it a single argument, which is the function object.  
  18.         """  
  19.         print "Inside __call__()"  
  20.         def wrapped_f(*args):  
  21.             print "Inside wrapped_f()"  
  22.             print "Decorator arguments:", self.arg1, self.arg2, self.arg3  
  23.             f(*args)  
  24.             print "After f(*args)"  
  25.         return wrapped_f  
  26.   
  27. @decoratorWithArguments("hello""world"42)  
  28. def sayHello(a1, a2, a3, a4):  
  29.     print 'sayHello arguments:', a1, a2, a3, a4  
  30.   
  31. print "After decoration"  
  32.   
  33. print "Preparing to call sayHello()"  
  34. sayHello("say""hello""argument""list")  
  35. print "after first sayHello() call"  
  36. sayHello("a""different""set of""arguments")  
  37. print "after second sayHello() call"  
From the output, we can see that the behavior changes quite significantly: 
Inside __init__()
Inside __call__()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

Now the process of decoration calls the constructor and then immediately invokes __call__(), which can only take a single argument (the function object) and must return the decorated function object that replaces the original. Notice that __call__() is now only invoked once, during decoration, and after that the decorated function that you return from __call__() is used for the actual calls. 

Although this behavior makes sense -- the constructor is now used to capture the decorator arguments, but the object __call__() can no longer be used as the decorated function call, so you must instead use __call__() to perform the decoration -- it is nonetheless surprising the first time you see it because it's acting so much differently than the no-argument case, and you must code the decorator very differently from the no-argument case. 

Decorator Functions with Decorator Arguments 
Finally, let's look at the more complex decorator function implementation, where you have to do everything all at once: 
  1. def decoratorFunctionWithArguments(arg1, arg2, arg3):  
  2.     def wrap(f):  
  3.         print "Inside wrap()"  
  4.         def wrapped_f(*args):  
  5.             print "Inside wrapped_f()"  
  6.             print "Decorator arguments:", arg1, arg2, arg3  
  7.             f(*args)  
  8.             print "After f(*args)"  
  9.         return wrapped_f  
  10.     return wrap  
  11.   
  12. @decoratorFunctionWithArguments("hello""world"42)  
  13. def sayHello(a1, a2, a3, a4):  
  14.     print 'sayHello arguments:', a1, a2, a3, a4  
  15.   
  16. print "After decoration"  
  17.   
  18. print "Preparing to call sayHello()"  
  19. sayHello("say""hello""argument""list")  
  20. print "after first sayHello() call"  
  21. sayHello("a""different""set of""arguments")  
  22. print "after second sayHello() call"  
Here's the output: 
Inside wrap()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

The return value of the decorator function must be a function used to wrap the function to be decorated. That is, Python will take the returned function and call it at decoration time, passing the function to be decorated. That's why we have three levels of functions; the inner one is the actual replacement function. 

Because of closures, wrapped_f() has access to the decorator arguments arg1arg2 and arg3, without having to explicitly store them as in the class version. However, this is a case where I find "explicit is better than implicit," so even though the function version is more succinct I find the class version easier to understand and thus to modify and maintain.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!