這幾天花了點時間了解了下Python的裝飾器。其實以前在書上也看過有關的內容,不過當時不理解。今天把自己的一點體會寫出來跟大家分享一下。
網上流傳得比較廣的,有關python裝飾器的文章有兩篇,一篇是CSDN上的,另外一篇是園子里的。附帶鏈接如下:
http://blog.csdn.net/thy38/article/details/4471421
http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
我個人比較喜歡園子里的那篇,講得很透徹,能讓大家對裝飾器有個大概的了解。至於CSDN那篇,我不太清楚他的python版本,他給出來的 “裝飾器語法--無參數裝飾器” 在我的機器上運行時是有問題的,稍后我會詳細跟大家討論。
一、裝飾器能干啥?
正如 AstralWind 在他的博客中介紹,“裝飾器是一個很著名的設計模式,經常被用於有切面需求的場景,較為經典的有插入日志、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。”
二、如何編寫自己的裝飾器?
讓我們來編寫一個比較簡單的裝飾器,在Python里面代碼看起來會是這樣的:
1 #!/usr/bin/env python 2 3 def deco(func): 4 def wrapper(): 5 print "Wrap start" 6 func() 7 print "Wrap end\n" 8 return wrapper 9 10 @deco 11 def foo(): 12 print "In foo():" 13 14 foo()
運行起來是這個樣子的:
1 $ python decorate.py 2 Wrap start 3 In foo(): 4 Wrap end
我們可以在“Wrap start”和“Wrap end”里面做一些自己想要的功能,比如計算運行時間,輸出日志等等。
三、為什么要返回一個函數?
我第一次看到“return wrapper”這句的時候,就在想為什么要返回一個函數?
正好CSDN上的文章是沒返回函數的,讓我們來仔細分析一下代碼:
1 def deco(func): 2 print func 3 return func 4 @deco 5 def foo():pass 6 7 foo()
運行的結果看起來很完美,但是只是看起來。讓我們把代碼修改一下:
1 >>> def deco(func): 2 ... print "In deco" 3 ... return func 4 ... 5 >>> @deco 6 ... def foo(): 7 ... print "In foo" 8 ... 9 In deco 10 >>>
等等,代碼里面還沒有調用 foo(),怎么就print “In deco”了?
這里編寫的裝飾器根本就沒有達到我們要求的功能,因為它返回的還是 func 本身,print “In deco” 也只會在初始化的時候運行一次,僅僅一次。
讓我們回到剛開始的例子,在 deco 里面返回了一個 wrapper 函數對象。可以試着這么理解,deco的作用是給 func 進行裝飾,wrapper 就是被裝飾過的func。
然后我們就可以重復調用這個被裝飾過的函數。
四、怎么裝飾一個 需要傳參數 的函數?
現在另一個問題又來了,前面被裝飾的函數都是不帶參數的,那帶參數的函數要怎么裝飾呢?
讓我們先嘗試下前面的辦法:
1 >>> def deco(func): 2 ... def wrapper(): 3 ... print "Wrap start" 4 ... func() 5 ... print "Wrap end\n" 6 ... return wrapper 7 ... 8 >>> @deco 9 ... def foo(x): 10 ... print "In foo():" 11 ... print "I have a para: %s" % x 12 ... 13 >>> foo('x') 14 Traceback (most recent call last): 15 File "<stdin>", line 1, in <module> 16 TypeError: wrapper() takes no arguments (1 given)
報了個缺少參數的錯誤,那把這個參數帶上:
1 >>> def deco(func): 2 ... def wrapper(x): 3 ... print "Wrap start" 4 ... func(x) 5 ... print "Wrap end\n" 6 ... return wrapper 7 ... 8 >>> @deco 9 ... def foo(x): 10 ... print "In foo():" 11 ... print "I have a para: %s" % x 12 ... 13 >>> foo('x') 14 Wrap start 15 In foo(): 16 I have a para: x 17 Wrap end
現在可以正常傳遞參數了。
五、怎么裝飾 參數列表不一樣 的多個函數?
繼續發散一下,要是想裝飾多個函數,但是這些函數的參數列表變化很大的呢?
這個時候,就到了使用Python參數魔法的時候了。
定義函數時:*params:收集其余的位置參數,返回元組。 **params:收集其余的關鍵字參數,返回字典。
調用函數時:*params:將元組拆分為位置參數傳入。 **params:將字典拆分為關鍵字參數傳入。
利用上面的參數魔法后,代碼看起來會是這樣的:
1 #!/usr/bin/env python 2 3 def deco(func): 4 def wrapper(*args, **kwargs): 5 print "Wrap start" 6 func(*args, **kwargs) 7 print "Wrap end\n" 8 return wrapper 9 10 @deco 11 def foo(x): 12 print "In foo():" 13 print "I have a para: %s" % x 14 15 @deco 16 def bar(x,y): 17 print "In bar():" 18 print "I have two para: %s and %s" % (x, y) 19 20 @deco 21 def foo_dict(x,z='dict_para'): 22 print "In foo_dict:" 23 print "I have two para, %s and %s" % (x, z) 24 25 if __name__ == "__main__": 26 foo('x') 27 bar('x', 'y') 28 foo_dict('x', z='dict_para')
運行一下看看效果:
1 $ python decorate.py 2 Wrap start 3 In foo(): 4 I have a para: x 5 Wrap end 6 7 Wrap start 8 In bar(): 9 I have two para: x and y 10 Wrap end 11 12 Wrap start 13 In foo_dict: 14 I have two para, x and dict_para 15 Wrap end
本人水平有限,以上如有錯誤,歡迎指正,謝謝大家^_^。