2.23. 裝飾器

裝飾器 是一種語法,它將一個函式(或方法)包裹在另一個函式中。包裹器可以在原始呼叫前後新增行為、替換回傳值,或附加中介資料。其形式為:

@wrapper
def f():
    ...

@wrapper 這一行等同於 f = wrapper(f) -- 函式 f 先正常建立,接著交給 wrapper,其結果再重新繫結到名稱 f

一個函式從左側進入「包裹器」方框, 並從右側以裝飾過的函式形式浮現。

裝飾器接收一個函式,並回傳一個新的函式。

2.23.1. 內建的方法裝飾器

Python 內建了少數幾個裝飾器,用於類別主體中。

2.23.1.1. @property

將方法轉變為 計算屬性。呼叫者像存取普通屬性一樣存取它(不加括號),但每次讀取時都會執行一個方法:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

當某個 看起來 簡單的屬性背後需要進行少量計算時,可使用它。如果這項工作很耗費資源,則改用一般方法 -- 呼叫者不會預期讀取屬性會很慢。

2.23.1.2. @classmethod

定義一個以 類別 而非實例作為第一個引數的方法。第一個參數依慣例命名為 cls

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

類別方法是提供 替代建構子 的標準方式 -- 也就是以非預設方式建立並回傳實例的工廠函式。

2.23.1.3. @staticmethod

定義一個既不接收實例也不接收類別的方法 -- 它只是一個基於組織考量而存在於類別命名空間中的普通函式:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

請審慎使用;如果某個函式其實與類別的狀態毫無關係,使用普通的模組層級函式通常更為清楚。

2.23.2. 撰寫自訂裝飾器

裝飾器是一個接收函式並回傳函式的函式。最精簡的形式:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

輸出:

calling add

wrapperfunc 形成閉包,並將一切轉發給它。*args / **kwargs 讓它能作用於任何函式,而不僅限於只有兩個引數的函式。這種模式是更複雜裝飾器(計時、快取、失敗重試)的基礎,但其核心始終相同:接收一個函式,回傳一個函式。