專項練習 · 9 分鐘閱讀

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

通用包裝函式

你的練習清單

常見問題

裝飾器可以接收參數嗎?

可以。這時需要再加一層:先接收參數,再回傳真正的裝飾器。

@ 會立刻改變函式嗎?

它會在定義時替換名字。包裝函式的函式體要等到新名字被呼叫時才執行。

裝飾器只能用於函式嗎?

類也可以被裝飾,但先從函式開始。只要一個可呼叫物件能被另一個可呼叫物件替換,同樣的思路就適用。

練習裝飾器

12 道題,覆蓋包裝函式、@ 語法、functools.wraps 和參數轉發。

打開練習

繼續閱讀: