hello大家好~~我是稀里糊塗林老冷,一天天稀里糊塗的。
前一段時間學習了裝飾器,覺着這東西好高大上哇靠!!哈哈,一定要總結一下,方便以后自己查閱,也希望幫助其他伙伴們共同進步!
裝飾器:
大家可以這樣理解,裝飾器是運用閉包的基本原理,對一個目標函數進行裝飾。即是在執行一個目標函數之前、之后執行一些特定的事情。
學習裝飾器一定要有閉包的基礎知識,如果對閉包不是特別理解的話,可以參考我之前的博文http://www.cnblogs.com/Lin-Yi/p/7305364.html,也可以學習其他大神的博文哦~~~一定要弄明白閉包,要不然裝飾器很難理解的。
不理解的話舉個例子:
工作中,他寫了登錄功能,我寫了注銷功能,他寫了保存個人信息功能.........你和你的伙伴是一個團隊,你們完成各種各樣的功能。但是再軟件上線的時候呢,技術總監說了,我們要統計每一個功能模塊執行耗費的時間,針對耗費時間太多的功能,可能我們要開會商討進行優化。
問題來了,我們團隊五六個人,每個人寫了兩三個功能,都要統計執行實現,是不是每個人都要修改自己的代碼,在功能模塊函數開始的時候取一下時間,結束的時候取一下時間,然后做一個減法。每一個人的每一個功能都去增加這樣一些代碼,是不是可麻煩了呢!是不是可浪費時間了呢!!那怎么辦?? 用裝飾器!
我們只需要編寫一個裝飾器,用這個裝飾器對我們寫的所有功能進行裝飾,就能達到同一個目的!!哈哈哈,很牛有木有!我們一起看看吧~
先上一段代碼!!!
1 import time 2 #裝飾器會用到閉包的原理:外函數內部定義了一個內函數,內函數使用外函數的局部變量,並且外函數返回了內函數的引用
3 #裝飾器函數 傳入一個我們想對他裝飾的目標函數的引用,將在內函數中使用
4 def decorator( func ): 5 def inner(): 6 t1 = time.time() # 目標函數開始之前取一下時間
7 print("登錄前的時間是",t1) 8 # 這個func外部函數傳入的參數,也就是我們希望裝飾的目標函數的引用
9 # 這里實際上執行了目標函數,我們想對這個函數進行裝飾,所有在它執行之前和之后進行一番操作,具體什么操作看業務邏輯
10 func() 11 t2 = time.time() # 目標函數結束之后取一下時間
12 print("登錄后的時間是",t2) 13 t = t2 - t1 #計算目標函數執行花了多長時間
14 print("登錄花費的時間:",t) 15 #在外函數結束時候返回內函數的引用
16 return inner 17
18 # 上面是我們寫的裝飾器函數,用裝飾器函數可以對一個目標函數進行操作!!
19 '''
20 下面的 @decorator 就是是使用裝飾函數decorator的意思 21 它實際上執行的是: login = decorator( login ) 22 注意,編程語言都是從右向左來解析執行,這句話實際上會發生的事情是: 23 1 把目標函數login(是一個變量名,里面存的是目標函數的引用) 傳入decorator函數被參數func接收了 24 這時候func也是目標函數的引用 func和login指向同一個函數對象 25 2 decorator函數定義了內部函數inner,發現里面會使用到func, 26 這用到閉包的原理,外函數結束的時候會把func綁定給內函數將來使用 27 3 外函數結束的時候把自己創建的內函數的引用inner返回給了login接收, 28 這時候login已經不是原來我們編寫的目標函數了,login實際上是一個inner函數的實例對象 29 再執行login() 的時候實際上執行了inner()的一個對象 30 4 再執行login() 的時候 實際上執行inner() 31 會先執行取時間 打印 32 之后執行func()才是執行我們的目標函數 33 最后又取時間 打印結果 34 '''
35 @decorator 36 #下面我們寫一個函數模擬是登錄功能模塊的函數,並且用裝飾器進行裝飾
37 def login(): 38 print("用戶正在登錄!請稍等。。。。") 39 time.sleep(1) 40 print("登錄成功!歡迎登錄!") 41
42
43
44 if __name__ == '__main__': 45 #調用login 實際上調用了裝飾之后的函數inner的一個對象,inner里的func才是真正的目標函數
46 login() 47 '''
48 執行結果: 49 登錄前的時間是 1502163280.2836263 50 用戶正在登錄!請稍等。。。。 51 登錄成功!歡迎登錄! 52 登錄后的時間是 1502163281.2895217 53 登錄花費的時間: 1.0058953762054443 54 '''
不知道大家有沒有看懂這段代碼。認真讀一讀注釋的話,我想有閉包基礎的伙伴們一定能讀懂裝飾器。
總之裝飾器很神奇,運用引用之間互相改變,實現了一段很厲害的功能!
但是呢,其實上面的裝飾器非常不好。我寫這樣一個裝飾器,只是為了幫助大家理解裝飾器的執行過程和實現的結果。
請大家繼續跟着我的思路:
我們先回顧一下:上面的目標函數login,當使用@decoration的意思是,login = decoration(login),我們把目標函數當作參數傳給了decoration,然后decoration返回了一個inner給login。這時候login就已經不再是之前的login了,而是裝飾函數里的inner函數的一個實例對象。當我們再執行login() 實際上執行了inner() ,原來的目標函數被裝飾器的func保存了,在inner里面決定了什么時候執行func。
但是問題來了!
1 如果我們想給這個login目標函數傳入參數:當調用login()的時候實際上調用的是inner(),如果我們想給login傳參,但是卻傳參給了inner,inner又沒有接收參數,這就會報錯!!!
2 如果我們login函數有返回值,調用login()實際調用了inner(),inner內執行的func()才是執行的目標函數,func執行的時候又沒有設置變量接收返回值,這樣目標函數的返回值就丟了!!!
為了讓我們的裝飾器能夠在各種各樣的目標函數上使用,不論有沒有參數,有沒有返回值,我們都不會丟數據也不會報錯,我們要對我們的裝飾器進行修改,讓它變得更加健壯!
一、通用的裝飾器模板:
上代碼! 我們把裝飾器函數簡化一下,不要他那么復雜。請看下面的裝飾器實例:
1 #裝飾器函數 傳入目標函數做參數
2 def decorator( func ): 3 #實際調用目標函數會發生調用inner,
4 # 所以我們讓inner接收不定參數,我們再把不定參數傳給目標函數func
5 #這樣不論我們傳入什么參數目標函數都能接收到!!
6 def inner( *args , **kwargs ): 7 print("裝飾器函數中。。目標函數執行之前的操作!!") 8 # 我們設置一個變量接收目標函數的返回值,在inner結束的時候再把返回值返回去
9 # python不同於其他語言,即使我們沒有編寫func的返回值,也會默認返回None,所以這里不會報錯
10 res = func( *args , **kwargs ) 11 print("裝飾器函數中。。目標函數執行之后的操作!!") 12 return res 13 return inner 14 #這樣編寫的裝飾器,在外部看來,我們就可以傳入參數給目標函數
15 # 同時也可以正常接收目標函數返回的參數!!!
16
17 @decorator #實際上會發生 destination = decorator( destination )
18 # 把目標函數傳入裝飾器函數返回了inner給destination
19 # 此后我們再調用destination 實際上調用了inner函數的一個對象
20 def destination( a ): 21 print( "目標函數接受到參數:%s"%a ) 22 return "目標函數的返回值%s"%a 23
24 if __name__ == '__main__': 25 # 這里實際上調用了inner函數的對象,我們傳入參數和接收返回值都沒有問題了!
26 res = destination(10) 27 # 打印一下返回值!
28 print(res) 29 '''
30 執行結果: 31 裝飾器函數中。。目標函數執行之前的操作!! 32 目標函數接受到參數:10 33 裝飾器函數中。。目標函數執行之后的操作!! 34 目標函數的返回值10 35 '''
以上這個裝飾器,實際上就可以作為一個通用版本的裝飾器來使用了,它基本可以為各種各樣的函數進行裝飾。
我們傳入目標函數的參數 在inner中設置不定參數去接收,又傳給了func
func的返回值我們設置了變量res去接,又在inner結束時候返回了res
這樣從外部看來,調用destination 只是單純的被包裝,並不知道里面這么復雜。
ok啦!!到這里呢,裝飾器基本就介紹完了,下面來一點拓展!
二、多層裝飾器嵌套:
1 #多層裝飾器的嵌套
2
3 #外層裝飾器函數
4 def decorator_out(func ): 5 def inner( *args , **kwargs ): 6 print("外裝飾器前置操作。。。。。。。。。") 7 res = func( *args , **kwargs) 8 print("外裝飾器后置操作..............") 9 return res 10 return inner 11
12 #內層裝飾器函數
13 def decorator_in( func ): 14 def inner( *args , **kwargs ): 15 print("內裝飾器前置操作。。。。。。。。。") 16 res = func( *args , **kwargs) 17 print("內裝飾器后置操作..............") 18 return res 19 return inner 20
21 #目標函數被兩個裝飾器裝飾
22 '''
23 這里實際上會發生的情況是 login = decorator_out( decorator_in( destination ) ) 24 先in裝飾器裝飾目標函數之后,把inner返回給destination, 25 然后 out裝飾器 再對新的destination進行裝飾,out里的func 存了 in裝飾器裝飾過的destination函數 26 又把inner返回給destination 27
28 這時候再執行目標函數destination() 實際上 29 1 執行out裝飾器的inner() 執行到func的時候 執行in裝飾器的inner 30 2 在內裝飾器inner中執行func才是原來的目標函數 31 3 目標函數執行完跳出到in裝飾器的inner 32 4 in裝飾器函數inner執行完 相當於out裝飾器的func執行完 跳到out裝飾器的inner中 33 5 out裝飾器執行結束,全部過程結束 34
35 '''
36 @decorator_out 37 @decorator_in 38 def destination(): 39 print("目標函數!") 40
41 if __name__ == '__main__': 42 destination() 43 '''
44 執行結果: 45 外裝飾器前置操作。。。。。。。。。 46 內裝飾器前置操作。。。。。。。。。 47 目標函數! 48 內裝飾器后置操作.............. 49 外裝飾器后置操作.............. 50 '''
當多層裝飾器嵌套的時候,實際上先內層裝飾器裝飾目標函數
外層裝飾器會對內層裝飾器裝飾的結果進行再裝飾
不太好理解呀!!我很害怕好伙伴們理解不了我想表達的事情。如果是在不理解,不妨敲一敲代碼,看一看注釋,也許就懂了!!我也是懵懵懂懂學過來的~
三、可選擇裝飾器:
接下來我們再對裝飾器來一個功能!
團隊中一個人寫了裝飾器,大家一塊用,之前的代碼中,我寫的裝飾器,都是對目標函數執行之前和之后都有一些操作。如果某一個功能不需要之前或者之后的操作,只需要一個操作怎么辦??
這時候用到三層閉包嵌套。我們來一段代碼理解一下
1 #我們傳入一個flag決定是不是要執行目標函數之后的操作
2 def flagOperation( flag ): 3 def decorator(func): 4 def inner(*args , **kwargs): 5 print("裝飾器前置操作。。。。。。。。") 6 res = func(*args , **kwargs) 7 if flag : #如果傳入flag是真則執行后置操作 否則不執行
8 print("我是后置操作") 9 return res 10 return inner 11 return decorator 12
13
14
15 '''
16 @flagOperation(True) 這里實際上先執行了flagOperation(True) ,返回了decorator裝飾器, 17 並且把flag的值是True綁定給了他 18 這時候用帶着flag的decorator 對目標函數進行裝飾 19 相當於@decorator 帶着一個flag 20 '''
21 @flagOperation(True) 22 def destination(): 23 print("目標函數!") 24
25 @flagOperation(False) 26 def desti2(): 27 print("目標函數2") 28
29 if __name__ == '__main__': 30 destination() 31 '''
32 結果: 33 裝飾器前置操作。。。。。。。。 34 目標函數! 35 我是后置操作 36 '''
37 desti2() 38 '''
39 結果: 40 裝飾器前置操作。。。。。。。。 41 目標函數2 42 '''
ok啦,到目前位置,已經探討了好多種牛逼的裝飾器了,他們功能非常強大。學習要站在巨人的肩膀上,我么能完成更加厲害的功能。
對於理解有困難的好伙伴,只要努力的把我分享的 通用的裝飾就可以了 其他可以不用理解。
四、類實現裝飾器
如果對一個類名后跟() , 實際上會調用類內的__call__方法,所以我們把裝飾器的邏輯寫到__call__ 方法里就可以!
1 #類裝配期 2 class Decorator(object): 3 def __init__(self,func): 4 self.func = func 5 def __call__(self): 6 print("類裝飾器前操作") 7 self.func() 8 print("類裝飾器后操作") 9 10 11 12 @Decorator 13 def login(): 14 print("login now") 15 16 if __name__ == '__main__': 17 login()
不裝飾器到這里還有一些小問題沒有解決。
目標函數是一個函數對象,它里面有自己的魔法屬性 函數名__name__ 、說明文檔__doc__等等。
如果我們目標函數被裝飾器裝飾之后,我們再調用 目標函數 destination.__name__ 會發現 本來 我們本來想要的 destination這個名字 打印出來確是 inner
這種情況確實不出乎意料,因為destination被裝飾之后確實存的就是inner了,我們對destination的操作實際上都是對inner的操作
但是這帶來了使用的不一致性。我們如果能把本來目標函數各種方法覆蓋給inner函數就好了!
當然這是能實現的,我們可以用functools 包里的工具來實現。這里就不探討了,再后續的博文中,我會再發表我學習functool模塊的一些常用工具!
希望大家有所收獲,我們共同進步!
歡迎其他朋友批評指正!