Python
單元:型別提示與靜態分析(Type Hints & Static Analysis)
主題:mypy 檢查
簡介
在 Python 中,型別提示(type hint)自 3.5 版起成為語言的正式功能,讓程式碼能以 自我說明 的方式描述參數、回傳值與變數的型別。雖然 Python 本身在執行時仍是動態型別語言,但透過型別提示,我們可以在開發階段使用 靜態分析工具 來提前捕捉錯誤、提升可讀性與維護性。
其中最常被使用的工具就是 mypy。mypy 會根據程式碼中的型別提示,執行「靜態型別檢查」── 在不執行程式的情況下找出可能的型別不匹配、遺漏或不合理的使用情形。對於 初學者,mypy 能幫助養成良好的型別思考習慣;對 中階開發者,則是確保大型專案在重構或多人協作時不會因型別錯誤而產生難以追蹤的 bug。
本篇文章將從 核心概念、實作範例、常見陷阱 與 最佳實踐,一步步帶你掌握 mypy 的使用方式,並說明它在真實專案中的應用情境。
核心概念
1. 為什麼需要 mypy?
- 提前發現錯誤:在執行前即捕捉傳入錯誤型別的呼叫。
- 提升可讀性:型別提示讓其他開發者能快速了解函式的預期行為。
- 支援 IDE:多數現代 IDE(如 VS Code、PyCharm)會根據
mypy的結果提供即時提示。
2. 基本安裝與使用
pip install mypy
執行檢查只需要在終端機中輸入:
mypy path/to/your_module.py
若檔案中沒有型別提示,mypy 仍會以 Any(任意型別)來推斷,檢查結果可能不會顯示太多資訊。因此,加入型別提示 才能發揮 mypy 的威力。
3. 常見型別提示語法
| 類型 | 語法 | 說明 |
|---|---|---|
| 基本型別 | int, str, bool |
直接寫在參數或回傳值後面 |
| 容器型別 | list[int], dict[str, float] |
需 from typing import List, Dict(Python 3.9 之後可直接使用 list[int]) |
可為 None 的型別 |
Optional[int] 或 `int |
None` |
| 任意型別 | Any |
用於暫時無法確定的情況,會關閉型別檢查 |
| Callable(可呼叫物件) | Callable[[int, str], bool] |
表示接受 int、str 兩個參數並回傳 bool 的函式 |
程式碼範例
以下示範 5 個常見情境,說明如何結合型別提示與 mypy 進行靜態檢查。每段程式碼皆以 註解 說明關鍵點,並示範執行 mypy 時可能得到的訊息。
範例 1:基本函式的型別提示
def add(a: int, b: int) -> int:
"""將兩個整數相加,回傳結果。"""
return a + b
result = add(3, 5) # 正確使用
# result = add(3, "5") # ← mypy: Argument 2 to "add" has incompatible type "str"; expected "int"
說明:若把第二個參數改為字串,mypy 會在檢查時報錯,提醒開發者型別不匹配。
範例 2:容器型別與泛型
from typing import List
def average(nums: List[float]) -> float:
"""計算數列的平均值。"""
return sum(nums) / len(nums)
avg = average([1.0, 2.5, 3.5])
# avg = average([1, 2, "3"]) # ← mypy: List element 2 has incompatible type "str"; expected "float"
說明:List[float] 限定了容器內只能是 float,混入其他型別會被 mypy 偵測。
範例 3:Optional 與 None 檢查
from typing import Optional
def greet(name: Optional[str]) -> str:
"""如果提供名字就打招呼,否則回傳預設訊息。"""
if name is None:
return "Hello, stranger!"
return f"Hello, {name}!"
msg1 = greet("Alice")
msg2 = greet(None) # 正常
# msg3 = greet(123) # ← mypy: Argument 1 to "greet" has incompatible type "int"; expected "Optional[str]"
說明:Optional[str] 等價於 str | None,mypy 可辨識出不接受 int 的錯誤。
範例 4:自訂類別與屬性型別
class User:
def __init__(self, username: str, age: int) -> None:
self.username: str = username
self.age: int = age
def is_adult(user: User) -> bool:
"""判斷使用者是否已滿 18 歲。"""
return user.age >= 18
alice = User("alice", 20)
bob = User("bob", 15)
print(is_adult(alice)) # True
print(is_adult(bob)) # False
# bob.age = "twenty" # ← mypy: Incompatible types in assignment (expression has type "str", variable has type "int")
說明:類別屬性若被錯誤賦值,mypy 也會報告型別不符。
範例 5:使用 Any 及其風險
from typing import Any, List
def flatten(data: List[Any]) -> List[Any]:
"""將多層次的列表展平成一層。"""
result: List[Any] = []
for item in data:
if isinstance(item, list):
result.extend(flatten(item)) # type: ignore # 暫時關閉型別檢查
else:
result.append(item)
return result
nested = [1, [2, [3, 4]], 5]
print(flatten(nested)) # [1, 2, 3, 4, 5]
說明:Any 會讓 mypy 放棄檢查,因此在使用時必須小心,並盡量加上 # type: ignore 以明確標示例外情況。
常見陷阱與最佳實踐
| 陷阱 | 可能的後果 | 建議的做法 |
|---|---|---|
| 忘記加入型別提示 | mypy 只能推斷 Any,錯過大部分檢查 |
逐步加入:先從函式簽名開始,逐漸擴展到變數與屬性 |
過度使用 Any |
靜態檢查失效,錯誤會在執行時才爆發 | 只在必須與外部庫交互時使用,並加 # type: ignore 註明 |
忽略 Optional |
可能產生 NoneType 錯誤 |
在返回值或參數可能為 None 時,必須使用 Optional[T] |
| 忽視第三方套件的型別資訊 | 介面不一致,導致錯誤 | 使用 pip install types-<package> 安裝型別存根(type stubs) |
未設定 mypy 配置 |
預設檢查太寬鬆或過於嚴格 | 建立 mypy.ini 或 pyproject.toml,根據專案需求調整 strict、ignore_missing_imports 等選項 |
最佳實踐
- 開啟嚴格模式:在
mypy.ini中加入strict = True,可一次性啟用多項嚴格檢查。 - 持續整合(CI):將
mypy加入 GitHub Actions、GitLab CI 等 pipeline,確保每次 PR 都通過型別檢查。 - 分階段導入:對於既有大型專案,可先對新模組或重構的程式碼執行
mypy,逐步提升覆蓋率。 - 結合
pytest:在測試失敗時,同時檢查型別,讓測試與型別檢查相輔相成。
實際應用場景
1. API 客戶端開發
在開發呼叫外部 REST API 的客戶端時,回傳的 JSON 可能缺少欄位或型別不符。使用 TypedDict 與 mypy,可以在編譯階段驗證資料結構是否符合預期,減少跑到生產環境才發現錯誤的風險。
from typing import TypedDict, List
class RepoInfo(TypedDict):
name: str
stars: int
forks: int
def parse_repo(data: dict) -> RepoInfo:
return {
"name": data["name"],
"stars": int(data["stargazers_count"]),
"forks": int(data["forks_count"]),
}
2. 資料科學與機器學習管線
資料前處理與模型訓練常涉及大量 numpy.ndarray、pandas.DataFrame 等大型結構。透過 mypy 搭配 numpy.typing、pandas-stubs,可以確保傳遞給模型的特徵矩陣形狀與型別正確,避免因維度錯誤產生的 RuntimeError。
3. 微服務與大型後端系統
在微服務架構中,各服務之間的介面(例如 gRPC、FastAPI)需要明確的資料合約。使用 pydantic 模型加上型別提示,mypy 能自動檢查 API 輸入輸出是否符合合約,提升服務間的相容性與可維護性。
總結
mypy是 Python 生態系中最成熟、最廣泛使用的 靜態型別檢查器,透過型別提示讓程式碼在編寫階段即得到「類型安全」的保護。- 從 基本函式、容器泛型、Optional、自訂類別 到 Any 的慎用,本文提供了完整的範例與說明,幫助讀者快速上手。
- 在實務上,嚴格模式、CI 整合、分階段導入 是提升專案品質的關鍵步驟;而在 API 開發、資料科學、微服務 等領域,型別提示與
mypy更是不可或缺的防護網。
藉由養成 型別思考 的習慣,搭配 mypy 的自動檢查,你的 Python 程式碼將變得更可讀、可維護、且更不易出錯。快把 mypy 加入每日開發流程,讓程式品質從「寫程式」升級到「寫安全的程式」吧!