1. 程式人生 > >1.Python裝飾器

1.Python裝飾器

裝飾器簡介

裝飾器,用於裝飾某個函式,或者方法,或者類

裝飾器可以用於某個函式或者方法或者類在執行之前或者執行後做某些操作,也可以理解為,為某個函式或者方法或者類新增功能

好處:是不用改變原來的函式,就可以給原來的函式新增功能

提示:裝飾器本身就是一個函式,只不過其傳入引數為一個函式或方法或類,我們這裡就主要指其引數為一個函式

 

方法:
1,定義一個函式,裡面傳一個值用於接收被裝飾函式的名稱(叫做裝飾器函式)

2,在裝飾器函式裡定義一個函式(叫做裝飾器功能函式)來寫新增功能和執行被裝飾函式

3,在裝飾器功能函式return返回被裝飾函式給裝飾器功能函式,並且在裝飾器函式return返回裝飾器功能函式給裝飾器函式

4,在被裝飾函式上面寫上 @裝飾器函式名 就可以呼叫執行裝飾器函式

 

重點:

只要函式應用上了裝飾器,那麼函式就會被重新定義,重新定義為裝飾器的內層函式,裝飾器內層函式的返回結果就等於應用了裝飾器的函式結果

 

較標準裝飾器的書寫

 1 #!usr/bin/python3
 2 from functools import wraps               # 匯入wraps下文會用到
 3 
 4 def decorator(func):                      # 裝飾器也是一個函式,其引數也為一個函式
 5     @wraps(func)                          #
利用 @wraps(func) 裝飾wrapper避免裝飾器改變原函式的名字 6 def wrapper(*args, **kwargs): # wrapper(*args, **kwargs) 萬能引數,不管被裝飾函式有多少引數,也不會出錯 7 print('我在被裝飾函式執行前執行!!') # 在被裝飾函式執行前執行的操作 8 func(*args, **kwargs) # 這裡同樣採用萬能引數 9 print('我在被裝飾函式執行後執行!!') # 在被裝飾函式執行後執行的操作
10 return wrapper # 返回值為函式wrapper,沒小括號代表不執行 11 12 13 @decorator # 裝飾器使用方法 14 def run(): 15 print('Hello World!') 16 # 用了裝飾器之後的函式實際上等價於: run = decorator(run) 17 # 因為裝飾器返回的是wrapper函式,所以: run = wrapper 18 19 run() # 所以呼叫run() 等價於呼叫 wrapper() 20 21 # 執行結果為: 22 # 我在被裝飾函式執行之前執行! 23 # Hello World! 24 # 我在被裝飾函式執行之後執行!

 

內容解析1:為什麼要用到萬能引數 (*args, **kwargs)

 1 from functools import wraps
 2 
 3 def decorator(func):                      
 4     @wraps(func)                          
 5     def wrapper():                        # 這裡取消了萬能引數,選擇不用引數
 6         print('我在被裝飾函式執行前執行!!') 
 7         func()                            # 這裡取消了萬能引數,選擇不用引數
 8         print('我在被裝飾函式執行後執行!!') 
 9     return wrapper                        
10 
11 
12 @decorator                                
13 def run():
14     print('Hello World!')
15 
16 @decorator
17 def fff(a):
18     print(a)
19 
20 run()     # run函式將能成功的呼叫
21 fff(5)    # fff函式將不能成功的呼叫

上面程式碼,取消了裝飾器功能函式和其中被傳入函式的萬能引數,使其無參。這樣,當裝飾器裝飾有參的函式時將會報錯,不能正確執行。因為用了裝飾器之後,本質上呼叫函式時呼叫的是wrapper函式,wrapper本身定義時是無參的,但呼叫的時候你卻給其傳入引數,其將不能正確執行。

同理,我們給定義裝飾器的時候給裝飾器功能函式wrapper一個引數,其對沒有引數的函式或者對有更多引數的函式進行裝飾的時候,也不能正確執行。

所以我們這裡就必須用到萬能引數 (*args, **kwargs),這個萬能引數代表可以沒有引數、也可以有多個引數、或者一個或多個關鍵字引數。這裡萬能引數只是我對它的一個稱呼,感覺這樣叫還是挺合理的。

 

內容解析2:from functools import wraps , @wraps(func) 的作用

1 def run():
2     print('Hello World!')
3 
4 print(run.__name__)  # 列印run函式的名字,將打印出run

我們單獨定義一個函式 run,打印出它的名字為 run 這是很正常的

 1 from functools import wraps
 2 
 3 def decorator(func):                      
 4     # @wraps(func)                        註釋掉了這一行           
 5     def wrapper(*args, **kwargs):                        
 6         print('我在被裝飾函式執行前執行!!') 
 7         func(*args, **kwargs)                            
 8         print('我在被裝飾函式執行後執行!!') 
 9     return wrapper                        
10 
11 
12 @decorator                                
13 def run():
14     print('Hello World!')
15 
16 print(run.__name__)       # 列印內容為: wrapper

這裡我們註釋掉了第四行的內容,用裝飾器裝飾函式 run,列印函式 run 的名字,會驚奇的發現函式 run 的名字竟然不是 run。其實這也不是太難理解,上文中提到呼叫函式 run 本質上就是呼叫裝飾器功能函式 wrapper。所以在使用一個裝飾器的時候,它會把原函式的名字改變掉,這在程式設計裡是一個十分危險的事情,所以必須加以避免。而避免方法就是從functools模組匯入wraps並用其去裝飾【裝飾功能函式】。

 1 from functools import wraps
 2 
 3 def decorator(func):                      
 4     @wraps(func)                               
 5     def wrapper(*args, **kwargs):                        
 6         print('我在被裝飾函式執行前執行!!') 
 7         func(*args, **kwargs)                            
 8         print('我在被裝飾函式執行後執行!!') 
 9     return wrapper                        
10 
11 
12 @decorator                                
13 def run():
14     print('Hello World!')
15 
16 print(run.__name__)       # 列印內容為: run

去掉註釋,用wraps裝飾【裝飾功能函式】,並且引數為 func,也就是裝飾器的引數。此時再列印函式 run 的名字,打印出 run,從而避免了裝飾器對原函式名字的影響。