每張訂單都要過同樣的三步:驗證、收款、列印。Maya 想給這條路徑計時,又不想改它。包裝函式該放在哪裡?
裝飾器是一個接收函式並回傳替代函式的函式。@ 語法是在定義時應用這種替代的簡潔寫法。包裝函式適合做日誌、計時、驗證和權限檢查,但應保留原函式的元資料。 你還會寫帶參數的裝飾器、轉發參數,並理解疊加順序。
函式可以作為值傳遞
函式也是物件。可以把它存進變數、傳給另一個函式,或作為回傳值。裝飾器正是利用這一點:接收原函式,並在它外面構造一個新的可呼叫物件。
def greet():
return "hello"
def call(func):
return func()
print(call(greet))把函式作為參數
用 @ 包裝函式
包裝函式通過閉包記住 func,並在被包裝函式呼叫時執行它。把 @announce 放在 greet 上方,等價於在定義之後寫 greet = announce(greet)。
def announce(func):
def wrapper():
return "Before: " + func()
return wrapper
@announce
def greet():
return "hello"
print(greet())裝飾器語法
保留原函式的名字
包裝函式替換了原函式物件,所以它的 __name__ 通常會變成 wrapper。functools.wraps 會把重要元資料複製回來。打算長期使用的裝飾器都應該加上它。
from functools import wraps
def announce(func):
@wraps(func)
def wrapper():
return func().upper()
return wrapper
@announce
def greet():
return "hello"
print(greet())
print(greet.__name__)保留函式身份
裝飾器工廠回傳裝飾器
裝飾器需要設定時,在外面再加一層函式。repeat(3) 回傳真正的裝飾器,後者再接收函式。三層容易混淆;只有行為確實需要參數時才用工廠。
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
return [func(*args, **kwargs) for _ in range(times)]
return wrapper
return decorator
@repeat(3)
def greet(name):
return f"hello {name}"
print(greet("Ada"))設定在外,函式在內
轉發參數和回傳值
包裝函式應接受 *args 和 **kwargs,並回傳原函式的結果。否則裝飾器會悄悄改變函式介面。再配合 functools.wraps,偵錯和文件仍能看到原來的名字。
def trace(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(func.__name__, args, kwargs, "->", result)
return result
return wrapper
@trace
def add(a, b=0):
return a + b
print(add(2, b=3))保持介面靈活,保留回傳值
疊加順序是從下往上
離 def 最近的裝飾器先應用。@bold 在 @upper 上方,意味著 upper 先包裝函式,然後 bold 再包裝結果。預測輸出時從下往上讀,雖然程式碼是從上往下寫的。
def bold(func):
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
def upper(func):
def wrapper():
return func().upper()
return wrapper
@bold
@upper
def title():
return "menu"
print(title())下面的裝飾器先執行
本課要點
- 裝飾器接收一個函式,並回傳一個可呼叫物件。
- @deco 放在 def f 上方,等價於 f = deco(f)。
- 內部包裝函式會在被裝飾函式呼叫時執行。
- functools.wraps 會保留原函式的元資料。
- 工廠回傳裝飾器;包裝函式轉發參數;疊加順序從下往上。
常見錯誤
- 裝飾器忘記回傳包裝函式。
- 在定義包裝函式時就呼叫原函式,而不是在包裝函式內部呼叫。
- 忘記 functools.wraps,導致原名和文件字串丟失。
- 裝飾器需要參數時,忘記多寫一層函式。
寫一個裝飾器,在函式回傳的字串兩邊加上方括號。
動手試試
改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。
練習本課
12 道題,作答後立即看到解釋。