淺顯易懂的談一談python中的裝飾器!!


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模塊的一些常用工具!

 

希望大家有所收獲,我們共同進步!

歡迎其他朋友批評指正!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM