前言
當我們的Python/ target=_blank class=infotextkey>Python代碼變得越來越復雜時,就可能會發現需要在函數中添加一些 額外的功能,例如 日志記錄、性能測試、輸入合法性檢查 等等。這時候,使用Python裝飾器就可以讓我們的代碼更加優雅和可維護。
裝飾器 是Python語言中的一種高級語法,它可以在不改變原有代碼的情況下,動態地為函數或者類添加功能。
本文小編將介紹裝飾器的實現原理、實現效果、適用場景,并且通過一些實際的例子來演示如何使用裝飾器來增強函數的功能和修改函數的行為。同時還有一些高階的裝飾器用法,例如類裝飾器、參數化裝飾器、多個裝飾器嵌套等等。
實現原理
裝飾器 的實現原理是利用了Python中的 閉包 和 函數對象 的特性。在Python中,函數和類都是一等公民,也就是說它們可以像普通變量一樣被傳遞、賦值、作為參數和返回值。
裝飾器實際上就是一個函數,它接受一個函數作為參數,并返回一個新的函數。在返回的新函數中,我們可以添加一些額外的功能,比如緩存函數、認證用戶、記錄日志、計時、權限驗證等等。然后再將這個新函數返回,從而實現對原有函數的裝飾。
實現效果
使用裝飾器可以使代碼更加簡潔、優雅。在不改變原有代碼的情況下,可以動態地添加、修改、刪除函數的功能。同時,裝飾器還可以提高代碼的復用性和可維護性,使代碼更加易讀易懂。
例如,我們可以使用裝飾器來給一個函數添加日志功能:
def log(func):
def wrApper(*args, **kwargs):
print(f"Calling {func.__name__} with args {args} and kwargs {kwargs}")
return func(*args, **kwargs)
return wrapper
@log
def add(a, b):
return a + b
print(add(1, 2))
輸出:
Calling add with args (1, 2) and kwargs
3
在上面的例子中,我們定義了一個log裝飾器,它接受一個函數作為參數,并返回一個新的函數wrapper。在wrapper函數中,我們先打印出函數的名稱和參數,然后再調用原有的函數,并將結果返回。最后,我們使用@log語法糖來裝飾add函數,從而實現了給add函數添加日志功能的效果。
適用場景
裝飾器可以用于很多場景,比如:
- 日志記錄:記錄函數的調用時間、參數和返回值等信息;
- 計時器:統計函數的執行時間;
- 緩存:緩存函數的計算結果,避免重復計算;
- 權限/身份驗證:驗證用戶是否有權限執行某個函數;
- 錯誤處理:捕獲函數執行過程中的異常,并進行處理等。
- 限制函數調用次數:給函數設定調用次數,單個進程或線程內或時間節點內不允許超出調用限制
- 重試:函數執行不符合預期時,進行重新調用執行
高階用法
除了基本的裝飾器語法外,還有一些高階的用法,可以讓裝飾器更加靈活和強大。
1、帶參數的裝飾器
有些時候,我們需要給裝飾器傳遞一些參數,比如日志的級別、緩存的大小等。為了實現帶參數的裝飾器,我們需要再定義一層函數,來接受裝飾器的參數,然后返回一個真正的裝飾器函數。
def log(level):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"[{level}] Calling {func.__name__} with args {args} and kwargs {kwargs}")
return func(*args, **kwargs)
return wrapper
return decorator
@log(level="INFO")
def add(a, b):
return a + b
print(add(1, 2))
輸出:
[INFO] Calling add with args (1, 2) and kwargs
3
在上面的例子中,我們定義了一個帶參數的裝飾器log,它接受一個參數level,并返回一個真正的裝飾器函數decorator。在decorator函數中,我們再定義一個wrapper函數,來實現日志記錄的功能。最后,我們使用@log(level="INFO")語法糖來裝飾add函數,并傳遞了一個參數level,從而實現了帶參數的裝飾器的效果。
2、類裝飾器
除了函數裝飾器外,Python中還支持類裝飾器。類裝飾器和函數裝飾器的實現原理是一樣的,只是它接受的參數是一個類,而不是一個函數。
class Log:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"Calling {self.func.__name__} with args {args} and kwargs {kwargs}")
return self.func(*args, **kwargs)
@Log
def add(a, b):
return a + b
print(add(1, 2))
輸出:
Calling add with args (1, 2) and kwargs
3
在上面的例子中,我們定義了一個類裝飾器Log,它接受一個函數作為參數,并在__call__方法中實現了日志記錄的功能。最后,我們使用@Log語法糖來裝飾add函數,并實現了類裝飾器的效果。
3、多裝飾器嵌套
多裝飾器嵌套也是一種高級用法,可以在一個函數上應用多個裝飾器,以實現更復雜的功能。裝飾器可以嵌套在一起,以便一個函數可以被多個裝飾器同時裝飾。
例如,我們可以定義一個裝飾器函數,用于記錄函數的執行時間:
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Time elapsed: {end_time - start_time:.2f} seconds")
return result
return wrapper
然后,我們可以定義另一個裝飾器函數,用于緩存函數的結果:
def cache(func):
cache_dict = {}
def wrapper(*args, **kwargs):
key = str(args) + str(kwargs)
if key in cache_dict:
print("Retrieving from cache...")
return cache_dict[key]
else:
result = func(*args, **kwargs)
cache_dict[key] = result
return result
return wrapper
現在,我們可以定義一個函數,并將它裝飾上述兩個裝飾器:
@cache
@timer
def my_function(x, y):
time.sleep(1)
return x + y
這意味著當我們調用 my_function 時,它會先被 cache 裝飾器裝飾,然后再被 timer 裝飾器裝飾。這樣,函數的結果會被緩存,并記錄函數的執行時間。
我們可以測試一下這個函數:
>>> my_function(2, 3)
Time elapsed: 1.00 seconds
5
>>> my_function(2, 3)
Retrieving from cache...
5
可以看到,第一次調用函數時,它需要花費 1 秒鐘的時間來執行。但是,第二次調用函數時,它會從緩存中獲取結果,而不需要再次執行函數。
總結
Python裝飾器是一種強大且靈活的機制,允許我們靈活的增強函數的功能和修改函數的行為。學會使用裝飾器可以使你的代碼更加健壯,具有更好的可重用性。同時,高階裝飾器可以讓你更靈活的處理多種需求。在實際開發中,我們可以根據具體的需求,來選擇合適的裝飾器,并使用高階用法來實現更加靈活和強大的功能。