1、簡單裝飾器
說明:代碼在下邊。裝飾前后,我們都打印一遍如下內容,做一下對比。
- print(foo) # 打印當前函數對象
- print(foo.__name__) # 打印foo函數的函數名
- print(foo.__doc__) # 打印foo函數的文檔字符串(docString)
裝飾之前:
- <function foo at 0x000002569AAB5620>
- foo
- this is foo
裝飾之后:
- <function check_result.<locals>.wrapper at 0x00000250E11F56A8>
- wrapper
- this is wrapper
可以看出,foo的引用發生了變化。裝飾的過程是這樣的,python解釋器會將foo作為參數傳遞給check_result函數,func就引用了foo函數,並將返回值給了foo,因為返回值wrapper引用了wrapper函數,所以foo的引用就變為了wrapper函數。這時再去執行foo,就等於是在執行wrapper函數了。需要注意的是在裝飾之后,我我們的foo函數並沒有銷毀,而是被func引用着。不然執行的時候也不會有結果了。可是怎么查看呢?使用 print(foo.__closure__[0].cell_contents) 則會看到輸出 <function foo at 0x000001F2C7465620> 。這樣我就找到了最開始定義的foo函數了!
1 ''' 2 簡單裝飾器: 3 實現裝飾后函數的功能是如果x和y相乘為負數,則返回0 4 ''' 5 6 def check_result(func): 7 '''hahah''' 8 def wrapper(*args, **kwargs): 9 '''this is wrapper''' 10 result = func(*args, **kwargs) 11 12 if result < 0: 13 return 0 14 else: 15 return result 16 return wrapper 17 18 19 @check_result 20 def foo(x, y): 21 '''this is foo''' 22 return x * y 23 24 25 26 27 # 裝飾過程拆解 28 # wrapper = check_result(foo) 29 # foo = wrapper
2、多層裝飾器
說明:在理解簡單裝飾器的基礎上,再來看多層的裝飾器,會很好理解。只需要關注一下多層裝飾器的裝飾順序和執行順序即可。
1 ''' 2 多層裝飾器 3 ''' 4 5 def deco1(func): 6 def wrapper1(*args, **kwargs): 7 '''this is wrapper1''' 8 print('start 1') 9 result = func(*args, **kwargs) 10 print('end 1') 11 return result 12 return wrapper1 13 14 15 def deco2(func): 16 def wrapper2(*args, **kwargs): 17 '''this is wrapper2''' 18 print('start 2') 19 result = func(*args, **kwargs) 20 print('end 2') 21 return result 22 return wrapper2 23 24 25 def deco3(func): 26 def wrapper3(*args, **kwargs): 27 '''this is wrapper3''' 28 print('start 3') 29 result = func(*args, **kwargs) 30 print('end 3') 31 return result 32 return wrapper3 33 34 35 @deco1 36 @deco2 37 @deco3 38 def foo(x, y): 39 '''this is foo''' 40 return x * y 41 42 print(foo(8, 9)) 43 ''' 44 輸出結果: 45 start 1 46 start 2 47 start 3 48 end 3 49 end 2 50 end 1 51 72 52 ''' 53 54 55 ''' 56 裝飾的過程: 57 wrapper3 = deco3(foo) 58 wrapper2 = deco2(wrapper3) 59 wrapper1 = deco1(wrapper2) 60 foo = wrapper1 61 62 63 執行的過程:正好和裝飾的過程相反。 64 foo(8, 9)--->wrapper1(8, 9)--->deco1(wrapper2)(8, 9)---> 65 | 66 v 67 deco1( deco2( deco3(foo) ) )(8, 9)<---deco1( deco2(wrapper3) )(8, 9) 68 類比穿衣服,穿(裝飾)的時候從里往外一層套一層,脫(執行)的時候從外到里一層一層脫。 69 '''
3、帶參裝飾器
說明:傳入函數執行的次數,統計執行完成的時間。
實現方法:使用工廠模式,定義一個工廠函數,它本身不是裝飾器,但是返回值是一個裝飾器,是用來"生產"裝飾器的。如下所示:
1 ''' 2 帶參裝飾器 3 ''' 4 5 import time 6 7 def timer(count): 8 def deco(func): 9 def wrapper(*args, **kwargs): 10 '''this is wrapper''' 11 t1 = time.time() 12 for i in range(count): 13 result = func(*args, **kwargs) 14 t2 = time.time() 15 print(t2 - t1) 16 return result 17 return wrapper 18 return deco 19 20 21 @timer(10000000) # 獲取foo執行10000000次的時間 22 def foo(x, y): 23 '''this is foo''' 24 return x * y
4、類裝飾器
說明:想要明白類裝飾器的原理,我們先要了解一下__call__這個方法。這個方法是python中所有能被調用的對象具有的內置方法,比如類,函數。調用類的過程就是得到一個類的實例的過程,如果要做類裝飾器我們得讓該類的實例可以被調用。裝飾器的本質也是一個函數被調用執行的過程。先看下面一段代碼:
1 class A: 2 pass 3 4 5 def foo(x, y): 6 return x, y 7 8 9 print(A.__call__) 10 print(foo.__call__) 11 12 print(foo.__call__(3, 5)) # 打印結果 15 13 print(foo(3, 5)) # 打印結果 15
打印結果為:可以看到,有__call__方法的才能被調用。還有一個方法判斷一個對象是否能被調用 callable() ,傳入對象,若返回True則表示能被調用,否則不能被調用。
<method-wrapper '__call__' of type object at 0x000002CE9522BF48>
<method-wrapper '__call__' of function object at 0x000002CE953B1E18>
根據這個特性,我們設計我們的類裝飾器:
1 ''' 2 類裝飾器 3 ''' 4 5 class Deco: 6 '''this is Deco''' 7 def __init__(self, func): 8 self.func = func 9 10 def __call__(self, *args, **kwargs): 11 result = self.func(*args, **kwargs) 12 if result < 0: 13 return 0 14 else: 15 return result 16 17 18 @Deco # 裝飾后若結果為負數則返回0 19 def foo(x, y): 20 '''this is foo''' 21 return x * y 22 23 ''' 24 裝飾過程如下: 25 這個時候foo引用了Deco的一個實例,執行foo()也就是實例調用__call__方法的過程。 26 ''' 27 foo = Deco(foo)