跳轉到

2004:裝飾器與 Python 2.4——@decorator 語法的誕生#

事件#

2004 年 11 月,Python 2.4 發佈,最重要的新語法是裝飾器(PEP 318):

@staticmethod
def foo(): ...

@classmethod
def bar(cls): ...

同年,內建 set/frozenset 型別完成整合(2.3 引入模組、2.4 成為內建),生成器運算式(generator expression,PEP 289)在 2.4 定型:sum(x*x for x in xs)。

為何重要#

  • @ 語法是史上爭論最久的 PEP 之一:PEP 318 從 2003 年討論到 2004 年,社群提出十幾種語法方案,最後 Guido 裁定 @——它成為 Python 最具識別度的語法之一,也是日後框架(Flask 路由、pytest fixture、dataclass)的基石。
  • set 型別補上了數學集合這一塊核心抽象;生成器運算式把列表推導式的語法與產生器的惰性結合。

理論與實用原因#

  • 理論原因(裝飾器):在 2.4 之前,「函式轉換器」(如 staticmethod、快取、授權檢查)只能寫在函式定義之後:def foo(): ...; foo = staticmethod(foo)——容易漏寫、位置遠離定義、可讀性差。理論上這是高階函式(函式作為引數回傳函式)的語法糖,源自 LISP 的 defun 包裝與 Haskell 的 combinators;@ 讓轉換在定義當下宣告,符合「宣告式優於命令式」。
  • 理論原因(set):集合論的基礎抽象;x in s 對 hash set 是 O(1),對 list 是 O(n)——理論上需要不同的資料結構才有不同複雜度。
  • 實用原因:Zope 與 Twisted 等大型框架大量使用函式轉換器,樣板碼氾濫是社群最強烈的痛點;@ 一行解決。

彌補了什麼缺陷#

  • 裝飾器彌補了「函式轉換寫在定義之後、重複樣板、易漏寫」的缺陷。
  • set 彌補了「只能用 dict 模擬集合、語意不清」的缺陷。
  • 生成器運算式彌補了「列表推導式立即配置整個列表記憶體」的缺陷(串流場景下浪费記憶體)。

程式範例#

裝飾器(PEP 318)——前後對比:

# 2.4 之前:轉換器寫在定義「之後」,易漏寫、離定義遠
def foo():
    ...
foo = staticmethod(foo)    # 忘了這行,foo 就不是 static method

def cache(func):           # 手寫函式轉換器
    memo = {}
    def wrapper(*args):
        if args not in memo:
            memo[args] = func(*args)
        return memo[args]
    return wrapper

# 2.4 之後:@ 讓轉換在「定義當下」宣告
@staticmethod
def foo():
    ...

@cache                     # 一行套用快取、授權檢查等橫切關注
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

set 型別——O(1) 成員查詢:

# 之前:只能用 dict 模擬集合
seen = {}
for x in xs: seen[x] = True

# 2.3/2.4:真正的集合
s = {1, 2, 3}
x in s                     # O(1)(hash set),list 是 O(n)
s & other                  # 數學運算:交集

生成器運算式(PEP 289)——惰性版列表推導式:

total = sum(x*x for x in xs)   # 不配置整個列表,逐項產出
# 對比:sum([x*x for x in xs]) 立即配置百萬元素也照樣吃記憶體

相關條目#