深入理解 Python 裝飾器中的參數傳遞與運作原理
更新日期: 2025 年 1 月 23 日
本文為 python 裝飾器系列文第三篇:
- 深入理解 Python 中的一等公民
- 理解 Python 裡的 @deprecated 裝飾器及其應用
- 深入理解 Python 裝飾器中的參數傳遞與運作原理 👈進度
- 深入理解Python 函式裝飾器的語法糖
- 深入理解 Python 裡的多層裝飾器設計與應用
我們通常使用裝飾器來增強函數的功能,例如記錄調用日誌、檢查輸入、或添加警告。
當被裝飾的函數需要接收參數時,裝飾器本身也需要處理這些參數,以確保功能的完整性。
本文將通過一個範例,講解如何設計能夠接收參數的裝飾器,以及裝飾器內部參數的運作機制。
基本範例:處理帶參數的函數
以下是一個裝飾器範例,用於提示函數即將過期:
def deprecated(fn):
def wrapper(*args, **kwargs): # 接收所有參數
print(f"函數 {fn.__name__} 要過期了!") # 提示警告
return fn(*args, **kwargs) # 傳遞參數並執行原函數
return wrapper
套用裝飾器
裝飾器 @deprecated
被用於一個接收參數的函數 hi
:
@deprecated
def hi(a, b):
print(a, b)
print("hello")
hi(1, 2) # 傳入參數 1 和 2
執行結果:
函數 hi 要過期了!
1 2
hello
裝飾器如何處理參數
wrapper(*args, **kwargs)
的角色
在裝飾器中,wrapper(*args, **kwargs)
是一個包裝函數,用於接收和處理被裝飾函數的所有參數。
*args
:接收所有位置參數,例如(1, 2)
。**kwargs
:接收所有關鍵字參數,例如{'key1': value1}
。
當 wrapper(*args, **kwargs)
被調用時,它會將接收到的參數完整傳遞給原始函數 fn
,並執行該函數。
關鍵代碼解析
return fn(*args, **kwargs)
這行代碼表示,wrapper
將捕獲到的所有位置參數和關鍵字參數,原封不動地傳遞給原函數 fn
。
這確保了裝飾器對原函數的功能沒有影響。
被裝飾函數的參數如何被傳遞
假設被裝飾的函數 hi
定義如下:
def hi(a, b):
print(a, b)
print("hello")
當調用 hi(1, 2)
時:
- 參數傳遞到
wrapper
- 位置參數
1, 2
被捕獲為*args = (1, 2)
。 - 無關鍵字參數,因此
**kwargs = {}
。
- 位置參數
wrapper
將參數傳給hi
- 透過
fn(*args, **kwargs)
,將*args
和**kwargs
解包後傳遞給hi
:a = 1
b = 2
- 透過
- 執行原函數
hi
hi
打印出1 2
和hello
。
這一過程確保了參數的完整性,並不會因裝飾器而影響原函數的正常運作。
流程圖:
執行流程圖:
1. @deprecated 裝飾:
hi = deprecated(hi) ---> 返回 wrapper 函數
2. 呼叫 hi(1,2):
hi(1,2) ---> wrapper(1,2)
|
+---> 印出警告 "函數 hi 要過期了!"
|
+---> fn(1,2) [原始 hi 函數]
|
+---> 印出 "1 2"
+---> 印出 "hello"
wrapper 函數包裝了原始的 hi 函數:
- 接收相同的參數 (1,2)
- 先執行警告訊息
- 再用相同參數呼叫原函數
常見疑問解析
問題:fn
是如何接收函數本身的?
在裝飾器中,fn
是指代被裝飾的函數。例如:
@deprecated
def hi(a, b):
print(a, b)
print("hello")
這相當於:
hi = deprecated(hi)
此時,fn
接收了原始函數 hi
的引用。
問題:裝飾器如何處理被調用時傳入的參數?
當執行 hi(1, 2)
時:
- 參數
1
和2
被傳遞到wrapper
中的*args
。 wrapper
透過解包操作*args, **kwargs
,將參數再次傳遞給原函數hi
。
具體代碼流程:
def wrapper(*args, **kwargs):
return fn(*args, **kwargs)
問題 3:*args
和 **kwargs
是如何解包的?
*args
和 **kwargs
是 Python 的特殊語法,用於解包參數:
*args
:將元組形式的參數逐一傳遞。**kwargs
:將字典形式的關鍵字參數逐一傳遞。
例如:
def test(a, b):
print(a, b)
args = (1, 2)
test(*args) # 相當於 test(1, 2)
總結
裝飾器在不改變函數本身的基礎上,靈活地為函數添加新功能。
在處理帶參數的函數時:
- 使用
*args
和**kwargs
捕獲所有位置參數與關鍵字參數。 - 使用
fn(*args, **kwargs)
將參數傳遞回原函數,確保功能一致性。
透過裝飾器,我們可以實現更多強大且模組化的功能設計,讓代碼結構更清晰、可維護性更高。
熟練掌握裝飾器的參數傳遞,是提升 Python 編程能力的重要一步!