在Python中,函數是“一等公民”——它可以被當作參數傳遞、賦值給變量,甚至作爲返回值。裝飾器正是利用了這一特性,讓我們能在不修改原函數代碼的前提下,給函數“動態添加功能”。這就像給禮物盒套上一個漂亮的包裝紙,既保留了禮物本身,又增添了新的外觀。
一、爲什麼需要裝飾器?¶
假設我們要給多個函數添加“打印執行日誌”的功能。如果直接修改每個函數,代碼會變得重複且難以維護。例如:
# 原始函數1
def add(a, b):
return a + b
# 原始函數2
def subtract(a, b):
return a - b
如果想給它們加日誌,最直接的方式是手動修改每個函數:
# 加了日誌的add函數
def add(a, b):
print(f"函數add開始執行,參數:a={a}, b={b}")
result = a + b
print(f"函數add執行結束,結果:{result}")
return result
# 加了日誌的subtract函數(重複代碼)
def subtract(a, b):
print(f"函數subtract開始執行,參數:a={a}, b={b}")
result = a - b
print(f"函數subtract執行結束,結果:{result}")
return result
這樣做顯然不優雅。如果有100個函數,就要重複寫100遍日誌代碼。裝飾器的出現,就是爲了解決這種“重複造輪子”的問題。
二、裝飾器的基本原理¶
裝飾器本質是一個函數,它接收一個函數作爲參數,並返回一個新函數(這個新函數“包裝”了原函數,添加了額外功能)。
1. 最簡單的裝飾器¶
我們先定義一個“日誌裝飾器”,它能給任何函數添加“開始/結束執行”的日誌:
def log_decorator(func): # 接收原函數func
def wrapper(*args, **kwargs): # 內部函數(包裝器)
# 1. 添加功能:打印開始日誌
print(f"函數 {func.__name__} 開始執行,參數:{args}, {kwargs}")
# 2. 調用原函數
result = func(*args, **kwargs)
# 3. 添加功能:打印結束日誌
print(f"函數 {func.__name__} 執行結束,結果:{result}")
return result
return wrapper # 返回包裝後的函數
2. 使用裝飾器¶
用 @ 語法糖(裝飾器語法)把原函數“包裹”起來:
@log_decorator # 相當於:add = log_decorator(add)
def add(a, b):
return a + b
@log_decorator # 同理:subtract = log_decorator(subtract)
def subtract(a, b):
return a - b
3. 調用測試¶
現在調用 add(1, 2) 時,實際執行的是 wrapper 函數,會自動打印日誌:
add(1, 2)
# 輸出:
# 函數 add 開始執行,參數:(1, 2), {}
# 函數 add 執行結束,結果:3
三、裝飾器的核心細節¶
1. *args 和 **kwargs¶
*args 接收位置參數(如 add(1, 2) 中的 1, 2),**kwargs 接收關鍵字參數(如 add(a=1, b=2) 中的 a=1, b=2)。這確保裝飾器能適配任何參數的函數。
2. 保留原函數信息¶
如果直接用上述裝飾器,add.__name__ 會變成 wrapper(因爲 add 被替換成了 wrapper)。這時可以用 functools.wraps 保留原函數的元信息:
from functools import wraps
def log_decorator(func):
@wraps(func) # 保留原函數信息
def wrapper(*args, **kwargs):
print(f"函數 {func.__name__} 開始執行")
result = func(*args, **kwargs)
print(f"函數 {func.__name__} 執行結束")
return result
return wrapper
# 現在查看函數名
print(add.__name__) # 輸出:add(而非 wrapper)
四、帶參數的裝飾器¶
如果需要給裝飾器傳遞參數(如不同的日誌前綴),可以嵌套一層函數:
def log_decorator(prefix="【日誌】"): # 外層函數:接收裝飾器參數
def decorator(func): # 內層函數:接收原函數
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix} 函數 {func.__name__} 開始執行")
result = func(*args, **kwargs)
print(f"{prefix} 函數 {func.__name__} 執行結束")
return result
return wrapper
return decorator # 返回內層裝飾器
# 使用帶參數的裝飾器
@log_decorator(prefix="【調試】")
def multiply(a, b):
return a * b
multiply(3, 4)
# 輸出:
# 【調試】 函數 multiply 開始執行
# 【調試】 函數 multiply 執行結束
五、裝飾器的應用場景¶
裝飾器非常靈活,常見場景包括:
- 日誌記錄:記錄函數調用時間、參數、返回值。
- 性能測試:統計函數執行耗時(如 @timer_decorator)。
- 權限驗證:調用函數前檢查用戶權限(如 @check_permission)。
- 緩存:緩存函數結果,避免重複計算(如 @cache_decorator)。
六、多個裝飾器的執行順序¶
如果多個裝飾器作用於同一函數,執行順序是從下往上(靠近函數的裝飾器先執行)。例如:
@decorator2
@decorator1
def func():
pass
執行順序是:decorator2 → decorator1 → func → decorator1 → decorator2。
總結¶
裝飾器是Python的“魔法工具”,通過函數嵌套和閉包思想,讓我們能優雅地給函數添加功能。核心要點:
1. 裝飾器是接收函數的函數,返回新函數。
2. @ 語法糖簡化了裝飾器的使用。
3. *args 和 **kwargs 處理任意參數,functools.wraps 保留原函數信息。
4. 帶參數的裝飾器需嵌套兩層函數。
從簡單的日誌到複雜的權限驗證,裝飾器讓代碼更簡潔、可維護。現在,你可以嘗試給一個自己的函數加上日誌裝飾器,體驗它的便捷了!