深入理解 Python 裝飾器中的參數傳遞與運作原理

更新日期: 2025 年 1 月 23 日

本文為 python 裝飾器系列文第三篇:

  1. 深入理解 Python 中的一等公民
  2. 理解 Python 裡的 @deprecated 裝飾器及其應用
  3. 深入理解 Python 裝飾器中的參數傳遞與運作原理 👈進度
  4. 深入理解Python 函式裝飾器的語法糖 
  5. 深入理解 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) 時:

  1. 參數傳遞到 wrapper
    • 位置參數 1, 2 被捕獲為 *args = (1, 2)
    • 無關鍵字參數,因此 **kwargs = {}
  1. wrapper 將參數傳給 hi
    • 透過 fn(*args, **kwargs),將 *args**kwargs 解包後傳遞給 hi
      • a = 1
      • b = 2
  1. 執行原函數 hi
    • hi 打印出 1 2hello

這一過程確保了參數的完整性,並不會因裝飾器而影響原函數的正常運作。

流程圖:

執行流程圖:

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) 時:

  • 參數 12 被傳遞到 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)

總結

裝飾器在不改變函數本身的基礎上,靈活地為函數添加新功能。

在處理帶參數的函數時:

  1. 使用 *args**kwargs 捕獲所有位置參數與關鍵字參數。
  2. 使用 fn(*args, **kwargs) 將參數傳遞回原函數,確保功能一致性。

透過裝飾器,我們可以實現更多強大且模組化的功能設計,讓代碼結構更清晰、可維護性更高。

熟練掌握裝飾器的參數傳遞,是提升 Python 編程能力的重要一步!

Similar Posts