Python 型別檢查策略:優先驗證公開 API
優先驗證公開 API 而非原始碼嚴格性
函式庫維護者應優先在測試套件上執行盡可能多的型別檢查器,而不是僅專注於其內部原始碼。雖然在原始碼上執行型別檢查器可以驗證內部邏輯,但在測試上執行多個檢查器可以確保公開 API 對於最廣泛的使用者群體保持相容性與可用性,無論他們使用哪種工具。
這種方法可以防止原始碼被大量的 # type: ignore 註解「污染」。因為不同的型別檢查器——例如 Mypy, Pyrefly, Pyright, ty, 和 Zuban——對於 Python typing spec 的解釋往往不同,或者具有不同的嚴格程度,試圖在實作中滿足所有檢查器可能會導致冗餘且令人分心的註解。
個案研究:Polars 與 __eq__ 的實作
在 Polars 函式庫中實作像 DataType.__eq__ 這樣的 method,說明了多檢查器原始碼驗證所造成的摩擦。在 Python 中,__eq__ 通常預期會回傳 bool。然而,在 Polars 中,此函數可以根據輸入回傳不同的型別,因此需要使用 overloads。
為了在原始碼中同時滿足 Mypy, Pyrefly, 和 ty,開發者可能被迫寫出如下的函數簽名:
@overload # type: ignore[override]
def __eq__( # pyrefly: ignore[bad-override]
self, other: pl.DataTypeExpr
) -> pl.Expr: ...
@overload
def __eq__(self, other: PolarsDataType) -> bool: ...
def __eq__(self, other: pl.DataTypeExpr | PolarsDataType) -> pl.Expr | bool: # ty: ignore[invalid-method-override] # pyright: ignore[reportIncompatibleMethodOverride]
這導致七行程式碼產生了四個不同的 type-ignore 註解。相反地,當相同的的功能透過測試套件進行驗證時,Mypy, Pyrefly, Pyright, ty, 和 Zuban 都能進行型別檢查而不會回報錯誤。這證明了雖然檢查器對於功能應該如何實作可能存在分歧,但它們對於公開 API 應該如何表現的行為通常是一致的。
理解 Python 型別檢查器領域 landscape
目前有五種主要的 Python 型別檢查器:Mypy, Pyrefly, Pyright, ty, 和 Zuban。它們之間的差異源於 Python typing spec 中的歧義,特別是關於如何處理定義不明確的型別資訊。
型別檢查器通常分為兩類:
- 嚴格檢查器 (Strict Checkers): 這些檢查器優先考慮防範潛在的 bug,並可能為了確保最大安全性而發出偽陽性 (false positives)。Pyrefly 被定位為一個嚴格、快速且符合規範的選項。
- 寬鬆檢查器 (Lenient Checkers): 這些檢查器允許更漸進地採用型別註解,減少在舊有程式碼庫中加入型別的初始摩擦。
社群觀點與挑戰
開發者之間的技術討論突顯了目前 Python typing 的幾個系統性挫折感:
碎片化與工具開銷
許多開發者發現檢查器的激增是 typing 系統「附加」上去的跡象。一些使用者回報同時執行 Pyright (在 CI) 和 Mypy (在本地) 因為它們能捕捉到不同類型的錯誤,例如 Pyright 在 overloads 方面較為嚴格,而 Mypy 在捕捉 None 問題方面更有效。
「錯誤牆」與推論
從 Mypy 轉向更嚴格的替代方案的開發者經常會遇到「錯誤牆」,特別是在像 Django 這樣的複雜框架中。開發者強烈希望有一個基於約束與推論的系統——即型別可以從使用方式(例如 + 運算子)中推論出來,而不是需要詳盡的手動註解。
靜態 vs. 動態的權衡
一些批評者認為,與從一開始就使用靜態型別語言相比,在 Python 中投入大量精力進行廣泛的型別檢查是低效的,因為靜態型別語言能同時提供 AI coding agents 的確定性與執行時性能的增益。
新興工具
新工具正在出現以彌補這一差距,例如 RightTyper,它透過監控程式執行並帶有約 25% 的執行時開銷,自動生成型別註解,讓開發者能夠將型別整合到現有的測試中,而無需手動撰寫。