淺談Python裝飾器

轉載自:http://blog.csdn.net/mdl13412/article/details/22608283

前置知識

一級對象

Python將一切視爲 objec t的子類,即一切都是對象,因此函數可以像變量一樣被指向和傳遞,我們來看下面的例子:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def foo():  
  2.     pass  
  3.       
  4. print issubclass(foo.__class__, object)  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. True  
上述代碼說明了Python 中的函數是 object 的子類,下面讓我們看函數被當作參數傳遞時的效果:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def foo(func):  
  2.     func()  
  3.   
  4. def bar():  
  5.     print "bar"  
  6.   
  7. foo(bar)  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. bar  

Python中的namespace

Python中通過提供 namespace 來實現重名函數/方法、變量等信息的識別,其一共有三種 namespace,分別爲:

  • local namespace: 作用範圍爲當前函數或者類方法
  • global namespace: 作用範圍爲當前模塊
  • build-in namespace: 作用範圍爲所有模塊

當函數/方法、變量等信息發生重名時,Python會按照 local namespace -> global namespace -> build-in namespace的順序搜索用戶所需元素,並且以第一個找到此元素的 namespace 爲準。
下面以系統的 build-in 函數 str 爲例進行說明:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def str(s):  
  2.     print "global str()"  
  3.   
  4. def foo():  
  5.     def str(s):  
  6.         print "closure str()"  
  7.     str("dummy")  
  8.   
  9. def bar():  
  10.     str("dummy")  
  11.   
  12. foo()  
  13. bar()  
首先定義三個 global namespace 的函數 strfoo 和bar,然後在 foo 函數中定義一個內嵌的 local namespace 的函數str,然後在函數 foo 和bar 中分別調用 str("dummy"),其運行結果如下所示:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. closure str()  
  2. global str()  
通過編碼實驗,我們可以看到:
  • foo 中調用 str 函數時,首先搜索 local namespace,並且成功找到了所需的函數,停止搜索,使用此namespace 中的定義
  • bar 中調用 str 函數時,首先搜索 local namespace,但是沒有找到str 方法的定義,因此繼續搜索 global namespace,併成功找到了str 的定義,停止搜索,並使用此定義
下面我們使用Python內置的 `ocals() 和 globals() 函數查看不同 namespace 中的元素定義:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. var = "var in global"  
  2.   
  3. def fun():  
  4.     var = "var in fun"  
  5.     print "fun: " + str(locals())  
  6.   
  7. print "globals: " + str(globals())  
  8. fun()  
運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__''a.py''__package__'None'fun': <function fun at 0x7f2ca74f66e0>, 'var''var in global''__name__''__main__''__doc__'None}  
  2. fun: {'var''var in fun'}  
通過運行結果,我們看到了 fun 定義了 local namespace 的變量var,在 global namespace 有一個全局的 var 變量,那麼當在global namespace 中直接訪問 var 變量的時候,將會得到 var = "var in global" 的定義,而在fun 函數的 local namespace 中訪問 var 變量,則會得到fun 私有的 var = "var in fun" 定義。

*args and **kwargs

  • *args: 把所有的參數按出現順序打包成一個 list
  • **kwargs:把所有 key-value 形式的參數打包成一個 dict

下面給出一個 *args 的例子:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. params_list = (12)  
  2. params_tupple = (12)  
  3.   
  4. def add(x, y):  
  5.     print x + y  
  6.   
  7. add(*params_list)  
  8. add(*params_tupple)  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. 3  
  2. 3  
**kwargs 的例子:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. params = {  
  2.     'x'1,  
  3.     'y'2  
  4. }  
  5.   
  6. def add(x, y):  
  7.     print x + y  
  8.   
  9. add(**params)  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. 3  

閉包

[plain] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. 閉包在維基百科上的定義如下: 在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。  
下面給出一個使用閉包實現的logger factory的例子:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def logger_facroty(prefix="", with_prefix=True):  
  2.     if with_prefix:  
  3.         def logger(msg):  
  4.             print prefix + msg  
  5.         return logger  
  6.     else:  
  7.         def logger(msg):  
  8.             print msg  
  9.         return logger  
  10.   
  11. logger_with_prefix = logger_facroty("Prefix: ")  
  12. logger_without_prefix = logger_facroty(with_prefix=False)  
  13. logger_with_prefix("msg")  
  14. logger_without_prefix("msg")  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Prefix: msg  
  2. msg  
在上面這個閉包的例子中,prefix 變量時所謂的自由變量,其在 return logger 執行完畢後,便脫離了創建它的環境logger_factory,但因爲其被logger_factory 中定義的 logger 函數所引用,其生命週期將至少和 logger 函數相同。這樣,在 logger 中就可以引用到logger_factory 作用域內的變量 prefix
將閉包與 namespace 結合起來:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. var = "var in global"  
  2.   
  3. def fun_outer():  
  4.     var = "var in fun_outer"  
  5.     unused_var = "this var is not used in fun_inner"  
  6.     print "fun_outer: " + var  
  7.     print "fun_outer: " + str(locals())  
  8.     print "fun_outer: " + str(id(var))  
  9.   
  10.     def fun_inner():  
  11.         print "fun_inner: " + var  
  12.         print "fun_inner: " + str(locals())  
  13.         print "fun_inner: " + str(id(var))  
  14.   
  15.     return fun_inner  
  16.   
  17. fun_outer()()  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. fun_outer: var in fun_outer  
  2. fun_outer: {'var''var in fun_outer''unused_var''this var is not used in fun_inner'}  
  3. fun_outer: 140228141915584  
  4. fun_inner: var in fun_outer  
  5. fun_inner: {'var''var in fun_outer'}  
  6. fun_inner: 140228141915584  
在這個例子中,當 fun_outer 被定義時,其內部的定義的 fun_inner 函數對 print "fun_inner: " + var中所引用的 var 變量進行搜索,發現第一個被搜索到的 var 定義在 fun_outer 的 local namespace 中,因此使用此定義,通過 print "fun_outer: " + str(id(var)) 和 print "fun_inner: " + str(id(var)),當var 超出 fun_outer 的作用域後,依然存活,而 fun_outer 中的unused_var 變量由於沒有被 fun_inner 所引用,因此會被GC

探索裝飾器

定義

[plain] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. 裝飾器在維基百科上的定義如下: A decorator is any callable Python object that is used to modify a function, method or class definition.   

基本語法

語法糖

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. @bar  
  2. def foo():  
  3.     print "foo"  
其等價於:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def foo():  
  2.     print "foo"  
  3. foo = bar(foo)  

無參數裝飾器

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def foo(func):  
  2.     print 'decorator foo'  
  3.     return func  
  4.  
  5. @foo  
  6. def bar():  
  7.     print 'bar'  
  8.   
  9. bar()  
foo 函數被用作裝飾器,其本身接收一個函數對象作爲參數,然後做一些工作後,返回接收的參數,供外界調用。

注意: 時刻牢記 @foo 只是一個語法糖,其本質是 foo = bar(foo)

帶參數裝飾器

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. import time  
  2.   
  3. def function_performance_statistics(trace_this=True):  
  4.     if trace_this:  
  5.        def performace_statistics_delegate(func):  
  6.             def counter(*args, **kwargs):  
  7.                 start = time.clock()  
  8.                 func(*args, **kwargs)  
  9.                 end =time.clock()  
  10.                 print 'used time: %d' % (end - start, )  
  11.             return counter  
  12.     else:  
  13.        def performace_statistics_delegate(func):  
  14.             return func  
  15.     return performace_statistics_delegate  
  16.  
  17. @function_performance_statistics(True)  
  18. def add(x, y):  
  19.     time.sleep(3)  
  20.     print 'add result: %d' % (x + y,)  
  21.  
  22. @function_performance_statistics(False)  
  23. def mul(x, y=1):  
  24.     print 'mul result: %d' % (x * y,)  
  25.   
  26. add(11)  
  27. mul(10)  
上述代碼想要實現一個性能分析器,並接收一個參數,來控制性能分析器是否生效,其運行效果如下所示:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. add result: 2  
  2. used time: 0  
  3. mul result: 10  
上述代碼中裝飾器的調用等價於:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. add = function_performance_statistics(True)(add(11))  
  2. mul = function_performance_statistics(False)(mul(10))  

類的裝飾器

類的裝飾器不常用,因此只簡單介紹。

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def bar(dummy):  
  2.     print 'bar'  
  3.   
  4. def inject(cls):  
  5.     cls.bar = bar  
  6.     return cls  
  7.  
  8. @inject  
  9. class Foo(object):  
  10.     pass  
  11.   
  12. foo = Foo()  
  13. foo.bar()  
上述代碼的 inject 裝飾器爲類動態的添加一個 bar 方法,因爲類在調用非靜態方法的時候會傳進一個self 指針,因此 bar 的第一個參數我們簡單的忽略即可,其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. bar  

類裝飾器

類裝飾器相比函數裝飾器,具有靈活度大,高內聚、封裝性等優點。其實現起來主要是靠類內部的 __call__ 方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法,下面時一個實例:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. class Foo(object):  
  2.     def __init__(self, func):  
  3.         super(Foo, self).__init__()  
  4.         self._func = func  
  5.   
  6.     def __call__(self):  
  7.         print 'class decorator'  
  8.         self._func()  
  9.  
  10. @Foo  
  11. def bar():  
  12.     print 'bar'  
  13.   
  14. bar()  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. class decorator  
  2. bar  

內置裝飾器

Python中內置的裝飾器有三個: staticmethodclassmethod 和property

staticmethod 是類靜態方法,其跟成員方法的區別是沒有 self 指針,並且可以在類不進行實例化的情況下調用,下面是一個實例,對比靜態方法和成員方法

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. class Foo(object):  
  2.     @staticmethod  
  3.     def statc_method(msg):  
  4.         print msg  
  5.   
  6.     def member_method(self, msg):  
  7.         print msg  
  8.   
  9. foo = Foo()  
  10. foo.member_method('some msg')  
  11. foo.statc_method('some msg')  
  12. Foo.statc_method('some msg')  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. some msg  
  2. some msg  
  3. some msg  
classmethod 與成員方法的區別在於所接收的第一個參數不是 self 類實例的指針,而是當前類的具體類型,下面是一個實例:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. class Foo(object):  
  2.     @classmethod  
  3.     def class_method(cls):  
  4.         print repr(cls)  
  5.   
  6.     def member_method(self):  
  7.         print repr(self)  
  8.   
  9. foo = Foo()  
  10. foo.class_method()  
  11. foo.member_method()  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. <class '__main__.Foo'>  
  2. <__main__.Foo object at 0x10a611c50>  
property 是屬性的意思,即可以通過通過類實例直接訪問的信息,下面是具體的例子:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. class Foo(object):  
  2.     def __init__(self, var):  
  3.         super(Foo, self).__init__()  
  4.         self._var = var  
  5.  
  6.     @property  
  7.     def var(self):  
  8.         return self._var  
  9.  
  10.     @var.setter  
  11.     def var(self, var):  
  12.         self._var = var  
  13.   
  14. foo = Foo('var 1')  
  15. print foo.var  
  16. foo.var = 'var 2'  
  17. print foo.var  
注意: 如果將上面的 @var.setter 裝飾器所裝飾的成員函數去掉,則Foo.var 屬性爲只讀屬性,使用 foo.var = 'var 2' 進行賦值時會拋出異常,其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. var 1  
  2. var 2  
注意: 如果使用老式的Python類定義,所聲明的屬性不是 read only的,下面代碼說明了這種情況:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. class Foo:  
  2.     def __init__(self, var):  
  3.         self._var = var  
  4.  
  5.     @property  
  6.     def var(self):  
  7.         return self._var  
  8.   
  9. foo = Foo('var 1')  
  10. print foo.var  
  11. foo.var = 'var 2'  
  12. print foo.var  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. var 1  
  2. var 2  

調用順序

裝飾器的調用順序與使用 @ 語法糖聲明的順序相反,如下所示:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     return func  
  4.   
  5. def decorator_b(func):  
  6.     print "decorator_b"  
  7.     return func  
  8.  
  9. @decorator_a  
  10. @decorator_b  
  11. def foo():  
  12.     print "foo"  
  13.       
  14. foo()  
其等價於:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     return func  
  4.   
  5. def decorator_b(func):  
  6.     print "decorator_b"  
  7.     return func  
  8.   
  9. def foo():  
  10.     print "foo"  
  11.   
  12. foo = decorator_a(decorator_b(foo))  
  13. foo()  
通過等價的調用形式我們可以看到,按照python的函數求值序列,decorator_b(fun) 會首先被求值,然後將其結果作爲輸入,傳遞給decorator_a,因此其調用順序與聲明順序相反。其運行結果如下所示:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. decorator_b  
  2. decorator_a  
  3. foo  

調用時機

裝飾器很好用,那麼它什麼時候被調用?性能開銷怎麼樣?會不會有副作用?接下來我們就以幾個實例來驗證我們的猜想。
首先我們驗證一下裝飾器的性能開銷,代碼如下所示:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     print 'func id: ' + str(id(func))  
  4.     return func  
  5.   
  6. def decorator_b(func):  
  7.     print "decorator_b"  
  8.     print 'func id: ' + str(id(func))  
  9.     return func  
  10.   
  11. print 'Begin declare foo with decorators'  
  12. @decorator_a  
  13. @decorator_b  
  14. def foo():  
  15.     print "foo"  
  16. print 'End declare foo with decorators'  
  17.   
  18. print 'First call foo'  
  19. foo()  
  20. print 'Second call foo'  
  21. foo()  
  22. print 'Function infos'  
  23. print 'decorator_a id: ' + str(id(decorator_a))  
  24. print 'decorator_b id: ' + str(id(decorator_b))  
  25. print 'fooid : ' + str(id(foo))  
其運行結果如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Begin declare foo with decorators  
  2. decorator_b  
  3. func id: 140124961990488  
  4. decorator_a  
  5. func id: 140124961990488  
  6. End declare foo with decorators  
  7. First call foo  
  8. foo  
  9. Second call foo  
  10. foo  
  11. Function infos  
  12. decorator_a id: 140124961954464  
  13. decorator_b id: 140124961988808  
  14. fooid : 140124961990488  
在運行結果中的:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Begin declare foo with decorators  
  2. decorator_b  
  3. func id: 140124961990488  
  4. decorator_a  
  5. func id: 140124961990488  
  6. End declare foo with decorators  
證實了裝飾器的調用時機爲: 被裝飾對象定義時
而運行結果中的:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. First call foo  
  2. foo  
  3. Second call foo  
  4. foo  
證實了在相同 .py 文件中,裝飾器對所裝飾的函數只進行一次裝飾,不會每次調用相應函數時都重新裝飾,這個很容易理解,因爲其本質等價於下面的函數簽名重新綁定:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. foo = decorator_a(decorator_b(foo))  
對於跨模塊的調用,我們編寫如下結構的測試代碼:
[ruby] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. .  
  2. ├── common  
  3. │   ├── decorator.py  
  4. │   ├── __init__.py  
  5. │   ├── mod_a  
  6. │   │   ├── fun_a.py  
  7. │   │   └── __init__.py  
  8. │   └── mod_b  
  9. │       ├── fun_b.py  
  10. │       └── __init__.py  
  11. └── test.py  
上述所有模塊中的 __init__.py 文件均爲: # -*- coding: utf-8 -*-
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. # -*- coding: utf-8 -*-  
  2. # common/mod_a/fun_a.py  
  3.   
  4.   
  5. from common.decorator import foo  
  6.   
  7.   
  8. def fun_a():  
  9.     print 'in common.mod_a.fun_a.fun_a call foo'  
  10.     foo()  

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. # -*- coding: utf-8 -*-  
  2. # common/mod_b/fun_b.py  
  3.   
  4. from common.decorator import foo  
  5.   
  6. def fun_b():  
  7.     print 'in common.mod_b.fun_b.fun_b call foo'  
  8.     foo()  
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. # -*- coding: utf-8 -*-  
  2. # common/decorator.py  
  3.   
  4. def decorator_a(func):  
  5.     print 'init decorator_a'  
  6.     return func  
  7.  
  8. @decorator_a  
  9. def foo():  
  10.     print 'function foo'  
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. # -*- coding: utf-8 -*-  
  2. # test.py  
  3.   
  4. from common.mod_a.fun_a import fun_a  
  5. from common.mod_b.fun_b import fun_b  
  6.   
  7. fun_a()  
  8. fun_b()  
上述代碼通過創建 common.mod_a 和 common.mod_b 兩個子模塊,並調用common.decorator 中的 foo 函數,來測試跨模塊時裝飾器的工作情況,運行 test.py 的結果如下所示:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. init decorator_a  
  2. in common.mod_a.fun_a.fun_a call foo  
  3. function foo  
  4. in common.mod_b.fun_b.fun_b call foo  
  5. function foo  
經過上面的驗證,可以看出,對於跨模塊的調用,裝飾器也只會初始化一次,不過這要歸功於 *.pyc,這與本文主題無關,故不詳述。
關於裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問題,更多的時候是我們設計上的問題,下面給出一個初學裝飾器時大家都會遇到的一個問題——丟失函數元信息:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def decorator_a(func):  
  2.     def inner(*args, **kwargs):  
  3.         res = func(*args, **kwargs)  
  4.         return res  
  5.     return inner  
  6.  
  7. @decorator_a  
  8. def foo():  
  9.     '''''foo doc'''  
  10.     return 'foo result'  
  11.   
  12. print 'foo.__module__: ' + str(foo.__module__)  
  13. print 'foo.__name__: ' + str(foo.__name__)  
  14. print 'foo.__doc__: ' + str(foo.__doc__)  
  15. print foo()  
其運行結果如下所示:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. foo.__module__: __main__  
  2. foo.__name__: inner  
  3. foo.__doc__: None  
  4. foo result  
我們可以看到,在使用 decorator_a 對 foo 函數進行裝飾後,foo 的元信息會丟失,解決方案參見: functools.wraps

多個裝飾器運行期行爲

前面已經講解過裝飾器的調用順序和調用時機,但是被多個裝飾器裝飾的函數,其運行期行爲還是有一些細節需要說明的,而且很可能其行爲會讓你感到驚訝,下面時一個實例:

[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def tracer(msg):  
  2.     print "[TRACE] %s" % msg  
  3.   
  4. def logger(func):  
  5.     tracer("logger")  
  6.     def inner(username, password):  
  7.         tracer("inner")  
  8.         print "call %s" % func.__name__  
  9.         return func(username, password)  
  10.     return inner  
  11.   
  12. def login_debug_helper(show_debug_info=False):  
  13.     tracer("login_debug_helper")  
  14.     def proxy_fun(func):  
  15.         tracer("proxy_fun")  
  16.         def delegate_fun(username, password):  
  17.             tracer("delegate_fun")  
  18.             if show_debug_info:  
  19.                 print "username: %s\npassword: %s" % (username, password)  
  20.             return func(username, password)  
  21.         return delegate_fun  
  22.     return proxy_fun  
  23.   
  24. print 'Declaring login_a'  
  25.  
  26. @logger  
  27. @login_debug_helper(show_debug_info=True)  
  28. def login_a(username, password):  
  29.     tracer("login_a")  
  30.     print "do some login authentication"  
  31.     return True  
  32.   
  33. print 'Call login_a'  
  34. login_a("mdl""pwd")  
大家先來看一下運行結果,看看是不是跟自己想象中的一致:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Declaring login_a  
  2. [TRACE] login_debug_helper  
  3. [TRACE] proxy_fun  
  4. [TRACE] logger  
  5. Call login_a  
  6. [TRACE] inner  
  7. call delegate_fun  
  8. [TRACE] delegate_fun  
  9. username: mdl  
  10. password: pwd  
  11. [TRACE] login_a  
  12. do some login authentication  
首先,裝飾器初始化時的調用順序與我們前面講解的一致,如下:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Declaring login_a  
  2. [TRACE] login_debug_helper  
  3. [TRACE] proxy_fun  
  4. [TRACE] logger  
然而,接下來,來自 logger 裝飾器中的 inner 函數首先被執行,然後纔是login_debug_helper 返回的 proxy_fun 中的 delegate_fun 函數。各位讀者發現了嗎,運行期執行login_a 函數的時候,裝飾器中返回的函數的執行順序是相反的,難道是我們前面講解的例子有錯誤嗎?其實,如果大家的認爲運行期調用順序應該與裝飾器初始化階段的順序一致的話,那說明大家沒有看透這段代碼的調用流程,下面我來爲大家分析一下。
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. def login_debug_helper(show_debug_info=False):  
  2.     tracer("login_debug_helper")  
  3.     def proxy_fun(func):  
  4.         tracer("proxy_fun")  
  5.         def delegate_fun(username, password):  
  6.             tracer("delegate_fun")  
  7.             if show_debug_info:  
  8.                 print "username: %s\npassword: %s" % (username, password)  
  9.             return func(username, password)  
  10.         return delegate_fun  
  11.     return proxy_fun  
當裝飾器 login_debug_helper 被調用時,其等價於:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. login_debug_helper(show_debug_info=True)(login_a)('mdl''pwd')  
對於只有 login_debug_helper 的情況,現在就應該是執行玩login_a輸出結果的時刻了,但是如果現在在加上logger 裝飾器的話,那麼這個login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')就被延遲執行,而將 login_debug_helper(show_debug_info=True)(login_a) 作爲參數傳遞給 logger,我們令 login_tmp = login_debug_helper(show_debug_info=True)(login_a),則調用過程等價於:
[python] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. login_tmp = login_debug_helper(show_debug_info=True)(login_a)  
  2. login_a = logger(login_tmp)  
  3. login_a('mdl''pwd')  
相信大家看過上面的等價變換後,已經明白問題出在哪裏了,如果你還沒有明白,我強烈建議你把這個例子自己敲一遍,並嘗試用自己的方式進行化簡,逐步得出結論。

一些實例參考

本文主要講解原理性的東西,具體的實例可以參考下面的鏈接:
Python裝飾器實例:調用參數合法性驗證

Python裝飾器與面向切面編程

Python裝飾器小結

Python tips: 超時裝飾器, @timeout decorator

python中判斷一個運行時間過長的函數

python利用裝飾器和threading實現異步調用

python輸出指定函數運行時間的裝飾器

python通過裝飾器和線程限制函數的執行時間

python裝飾器的一個妙用

通過 Python 裝飾器實現DRY(不重複代碼)原則

參考資料

Understanding Python Decorators in 12 Easy Steps

Decorators and Functional Python

Python Wiki: PythonDecorators

Meta-matters: Using decorators for better Python programming

Python裝飾器入門(譯)

Python裝飾器與面向切面編程

Python 的閉包和裝飾器

Python裝飾器學習(九步入門)

python 裝飾器和 functools 模塊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章