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]) 立即配置百萬元素也照樣吃記憶體