Python 裝飾器練習指南:包裝函式
裝飾器的想法很小,回報卻很大:函式可以傳給另一個函式,並被它的回傳值替換。理解這一點後,日誌、計時、快取和驗證就能變成可復用的層,而不是到處複製的程式碼。
函式也是普通物件
可以把函式賦給名字、作為參數傳遞,也可以回傳它。裝飾器正是利用這些能力:接收原函式,並回傳一個通常會呼叫它的可呼叫物件。
def greet():
return "hello"
def call(func):
return func()
print(call(greet))傳遞函式
構造包裝函式
內部函式通過閉包記住原函式,外層裝飾器回傳這個內部函式。在被裝飾函式呼叫之前,包裝函式裡的程式碼不會執行。
def announce(func):
def wrapper():
return "Before: " + func()
return wrapper
@announce
def greet():
return "hello"
print(greet())包裝函式稍後執行
讀懂 @ 簡寫
@ 行會在函式定義之後應用。@announce 放在 greet 上方,表示 greet = announce(greet)。此時名字 greet 已指向包裝函式,所以包裝函式必須顯式呼叫原函式。
def upper(func):
def wrapper():
return func().upper()
return wrapper
@upper
def word():
return "hi"
print(word())名字被包裝函式替換
用 wraps 保留元資料
沒有額外處理時,包裝函式的 __name__ 會變成 wrapper。functools.wraps 會複製原函式名、文件字串和相關屬性。偵錯、文件和檢查函式的工具都依賴這些資訊。
from functools import wraps
def upper(func):
@wraps(func)
def wrapper():
return func().upper()
return wrapper
@upper
def word():
return "hi"
print(word.__name__)保留原函式身份
轉發參數
沒有參數的包裝函式只能包裝沒有參數的函式。用 *args 和 **kwargs 接收原函式能接受的值,再原樣轉發。包裝函式自己的邏輯應放在呼叫原函式之外。
def announce(func):
def wrapper(*args, **kwargs):
return "Result: " + str(func(*args, **kwargs))
return wrapper
@announce
def add(a, b):
return a + b
print(add(2, 3))通用包裝函式
你的練習清單
- 記住裝飾器要回傳可呼叫物件。
- 外層函式要回傳包裝函式。
- 在包裝函式內部呼叫原函式。
- 用 functools.wraps 保留元資料。
- 函式有參數時轉發 *args 和 **kwargs。
常見問題
裝飾器可以接收參數嗎?
可以。這時需要再加一層:先接收參數,再回傳真正的裝飾器。
@ 會立刻改變函式嗎?
它會在定義時替換名字。包裝函式的函式體要等到新名字被呼叫時才執行。
裝飾器只能用於函式嗎?
類也可以被裝飾,但先從函式開始。只要一個可呼叫物件能被另一個可呼叫物件替換,同樣的思路就適用。
練習裝飾器
12 道題,覆蓋包裝函式、@ 語法、functools.wraps 和參數轉發。
繼續閱讀: