跳轉到

2019:海象運算子與 Python 3.8——指定運算式 :=#

事件#

2019 年 10 月,Python 3.8 發佈,主要新特性:

  1. 海象運算子(walrus operator)(PEP 572)::= 指定運算式——if (n := len(a)) > 10: ...
  2. 位置限定參數(PEP 570):def f(a, b, /, c, d): ...——/ 之前只能按位置傳入
  3. f-string = 除錯語法:f"{x=}" 同時輸出名稱與值
  4. typing.TypedDict/Literal/Final:型別系統持續擴充

為何重要#

  • PEP 572 是史上爭議最大的 PEP:討論過程中社群激烈分裂,甚至導致 Guido van Rossum 宣布辭去 BDFL(2018 年 7 月)——這是 Python 治理結構的歷史轉捩點(此後改由 Steering Council 委員會治理)。
  • := 讓「指定」第一次成為運算式:Python 自 1989 年以來堅持「指定是陳述式」(對照 C 的 = 回傳值),28 年後首次讓步——但用 := 符號與 = 明確區隔。

理論與實用原因#

  • 理論原因(:=):
  • 「指定是運算式」源自 C(1972)傳統,好處是消除「計算一次、判斷一次、使用一次」的重複呼叫:if (n := len(a)) > 10 比 n = len(a); if n > 10 少一行且不會漏。
  • Python 長期拒絕它是為了防 C 的經典錯誤 if (x = 5)(把比較誤寫成指定);海象運算子用不同符號 := 保留這個保護——這是「兼得兩者」的折衷設計。
  • 理論背景:表達式導向(expression-oriented)程式設計(Rust、ML 系語言的一切皆表達式);Python 選擇了局部採用。
  • 理論原因(位置限定 /):參數名是 API 的一部分,但內建函式(如 len(obj))的參數名是實作細節;/ 讓 API 設計者可以宣告「這些參數名不在公共契約內」,未來可自由改名不破壞相容性——這是 API 相容性契約的理論補完(*args 限制關鍵字傳入早在 Python 2 已有,/ 補上對稱的另一半)。
  • 實用原因:迴圈中反覆呼叫 len()/regex match 的場景(如 while (chunk := f.read(8192)):)是 := 的最大受益者;f-string = 則來自 print 除錯的日常痛點。

彌補了什麼缺陷#

  • := 彌補了「指定必須是獨立陳述式、導致計算結果重複呼叫或多一行樣板」的缺陷。
  • / 彌補了「參數名被迫成為公共 API、無法重構」的缺陷。
  • f-string = 彌補了「print 除錯要打兩次變數名」的缺陷。

程式範例#

海象運算子(PEP 572)——前後對比:

# 3.8 之前:計算一次、判斷一次、使用一次——重複呼叫或多一行樣板
n = len(data)
if n > 10:
    print(f"太長了: {n}")

# 3.8 之後::= 讓「指定」成為運算式
if (n := len(data)) > 10:
    print(f"太長了: {n}")

# 最大受益者:迴圈中的重複呼叫
while (chunk := f.read(8192)):        # 讀檔迴圈:一行取代兩行
    process(chunk)

# 對照 C 的經典錯誤:if (x = 5) 把比較誤寫成指定
# Python 用不同符號 := 保留這個保護——兼得兩者

位置限定參數(PEP 570)——API 契約的補完:

def positional_only(a, b, /, c, *, d):
    return a + b + c + d

positional_only(1, 2, c=3, d=4)   # a, b 只能按位置——名字不是公共契約
# positional_only(a=1, b=2, ...)  # TypeError

# 實例:內建函式的參數名是實作細節,未來可自由改名不破壞相容性
len(obj)            # 實際上是 len(obj, /)——"obj" 不在公共契約內

f-string = 除錯語法:

x = 42
print(f"{x = }")      # "x = 42"——名稱與值同時輸出,print 除錯免打兩次
print(f"{x * 2 = }")  # "x * 2 = 84"——表達式也行

相關條目#