Python裝飾器-面向切面的程式設計AOP2
這幾天花了點時間瞭解了下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