這幾天花了點時間瞭解了下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 @deco11 def foo():12 print "In foo():"13 14 foo()
運行起來是這個樣子的:
1 $ python decorate.py2 Wrap start3 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 deco10 >>>
等等,代碼裡面還沒有調用 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" % x12 ...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" % x12 ...13 >>> foo('x')14 Wrap start15 In foo():16 I have a para: x17 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 @deco11 def foo(x):12 print "In foo():"13 print "I have a para: %s" % x14 15 @deco16 def bar(x,y):17 print "In bar():"18 print "I have two para: %s and %s" % (x, y)19 20 @deco21 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 y10 Wrap end11 12 Wrap start13 In foo_dict:14 I have two para, x and dict_para15 Wrap end
本人水平有限,以上如有錯誤,歡迎指正,謝謝大家^_^。