裝飾器的完整實現及原理


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)

 


免責聲明!

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



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