- 什么是裝飾器?
裝飾器(Decorator)本質上是一個 修改其他函數功能的函數。它的核心思想是:不修改原函數代碼,動態添加新功能。比如:
記錄函數執行時間
檢查用戶權限
緩存計算結果
自動重試失敗操作
- 理解函數是“對象”
在 Python 中,函數也是對象,可以像變量一樣傳遞。這是裝飾器的核心基礎!
示例 1:函數賦值給變量
python
復制
def say_hello():print("Hello!")
將函數賦值給變量
greet = say_hello
調用變量(實際調用函數)
greet() # 輸出: Hello!
示例 2:函數作為參數傳遞
python
復制
def call_twice(func):func() # 第一次調用func() # 第二次調用call_twice(say_hello)
# 輸出:
# Hello!
# Hello!
- 最簡單的裝飾器
假設我們要在函數執行前后打印日志:
步驟 1:定義一個裝飾器函數
python
復制
def log_decorator(func):def wrapper():print("函數開始執行...")func() # 調用原函數print("函數執行完畢!")return wrapper # 返回新函數
步驟 2:使用裝飾器
python
復制
@log_decorator # 語法糖(等價于 say_hello = log_decorator(say_hello)
def say_hello():print("Hello!")say_hello()# 輸出:
# 函數開始執行...
# Hello!
# 函數執行完畢!
- 裝飾器的執行過程
當使用 @log_decorator 時,實際發生了以下步驟:
傳遞函數:log_decorator(say_hello) 被調用,參數是原函數 say_hello
返回新函數:log_decorator 返回內部定義的 wrapper 函數
替換原函數:say_hello 變量指向新的 wrapper 函數
- 處理帶參數的函數
如果被裝飾的函數需要參數,怎么辦?讓 wrapper 接受參數并傳遞給原函數。
示例:記錄函數參數
python
復制
def log_args(func):def wrapper(*args, **kwargs): # 接受任意參數print(f"參數: args={args}, kwargs={kwargs}")return func(*args, **kwargs) # 傳遞參數給原函數return wrapper@log_args
def add(a, b):return a + bprint(add(3, b=5))
# 輸出:
# 參數: args=(3,), kwargs={'b': 5}
8
- 裝飾器帶參數
如果裝飾器本身需要參數(比如指定日志級別),需要再包裹一層。
示例:根據日志級別打印
python
復制
def log_level(level): # 外層函數接受參數def decorator(func): # 裝飾器函數def wrapper(*args, **kwargs):print(f"[{level}] 函數開始執行...")result = func(*args, **kwargs)print(f"[{level}] 函數執行完畢!")return resultreturn wrapperreturn decorator # 返回裝飾器@log_level("INFO") # 等價于 add = log_level("INFO")(add)
def add(a, b):return a + badd(2, 3)
# 輸出:
# [INFO] 函數開始執行...
# [INFO] 函數執行完畢!
- 保留原函數的信息
使用裝飾器后,原函數的名稱(name)和文檔(doc)會被替換為 wrapper。用 functools.wraps 解決這個問題:
python
復制
from functools import wrapsdef log_decorator(func):@wraps(func) # 保留原函數信息def wrapper(*args, **kwargs):print("開始執行...")result = func(*args, **kwargs)print("執行完畢!")return resultreturn wrapper@log_decorator
def say_hello():"""打招呼的函數"""print("Hello!")print(say_hello.__name__) # 輸出: say_hello(而不是 wrapper)
print(say_hello.__doc__) # 輸出: 打招呼的函數
- 類裝飾器
除了函數,還可以用類實現裝飾器。通過實現 call 方法:
python
復制
class CountCalls:def __init__(self, func):self.func = funcself.calls = 0def __call__(self, *args, **kwargs):self.calls += 1print(f"函數被調用了 {self.calls} 次")return self.func(*args, **kwargs)@CountCalls
def say_hello():print("Hello!")say_hello() # 輸出: 函數被調用了 1 次 → Hello!
say_hello() # 輸出: 函數被調用了 2 次 → Hello
!
9. 實際應用場景
裝飾器在 Python 中廣泛應用,例如:
Web框架:@app.route(“/”)(Flask/Django)
權限驗證:@login_required
性能測試:計算函數執行時間
緩存:@lru_cache(內置裝飾器)
總結
核心思想:裝飾器通過“函數嵌套”和“函數作為參數”實現功能擴展。
關鍵點:
使用 @decorator 語法糖
處理參數:*args 和 **kwargs
保留原函數信息:@wraps(func)
類裝飾器:實現 call 方法
試著寫幾個自己的裝飾器(比如記錄執行時間),就能快速掌握這個概念!