Python 課程 – 型別提示與靜態分析
主題:pyright / pylance
簡介
在 Python 生態系統裡,型別提示(type hints) 已經從可選的語法糖,逐漸演變成大型專案維護與協作的基礎工具。搭配靜態分析器,開發者可以在寫程式的同時即時捕捉潛在錯誤、提升 IDE 補完品質,甚至在 CI/CD 流程中加入型別檢查,降低部署風險。
pyright 是 Microsoft 開源的高速型別檢查器,而 pylance 則是基於 pyright 的 VS Code 智慧感知(IntelliSense)擴充套件。兩者共同提供 即時、準確且效能佳 的型別分析體驗,對於想在 Python 中導入型別安全的開發者來說,是不可或缺的工具。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整掌握 pyright / pylance 的使用方式,並分享在真實專案中的應用場景。
核心概念
1. 為什麼需要靜態型別分析?
| 需求 | 傳統動態檢查 | 靜態型別分析 |
|---|---|---|
| 早期發現錯誤 | 只能在執行時才知道 | 編寫時即顯示錯誤 |
| IDE 補完與文件提示 | 只能靠 docstring | 完整的型別資訊 |
| 大型團隊協作 | 隱式約定易產生誤解 | 明確合約 (contract) |
| CI/CD 檢查 | 需要額外測試 | 直接在 lint 步驟完成 |
pyright 透過 AST(抽象語法樹) 與 型別推斷演算法,在毫秒級別完成分析,遠快於傳統的 mypy。pylance 把這個引擎嵌入 VS Code,讓開發者在編輯器裡即時看到錯誤、建議與型別資訊。
2. 安裝與基本設定
# 1. 全域安裝 pyright(可在任何編輯器使用)
npm install -g pyright # 需要 Node.js 環境
# 2. VS Code 安裝 pylance(Marketplace 直接安裝)
# 或者使用指令列安裝
code --install-extension ms-python.vscode-pylance
在專案根目錄建立 pyrightconfig.json,讓 pyright 知道要檢查的範圍與型別嚴格度:
{
"typeCheckingMode": "strict",
"exclude": ["**/tests/**", "dist/"],
"reportMissingImports": true,
"reportUnusedVariable": true
}
typeCheckingMode可設定為off、basic、strict。strict 會啟用所有嚴格檢查,最適合 CI/CD 使用。exclude用來排除不需要檢查的目錄,避免噪音。
3. 基本型別提示語法
def greet(name: str) -> str:
"""回傳問候語"""
return f"Hello, {name}!"
name: str表示name必須是字串。-> str表示函式回傳值的型別。
pyright 會在 呼叫 greet(123) 時即時報錯:
Argument of type "int" is not assignable to parameter "name" of type "str"
4. 常見型別工具
| 工具 | 用途 | 範例 |
|---|---|---|
List[T] / Dict[K, V] |
泛型容器 | def total(values: List[int]) -> int: |
Optional[T] |
可能為 None |
def find(key: str) -> Optional[int]: |
Union[A, B] |
多種可能型別 | def parse(data: Union[str, bytes]) -> str: |
TypedDict |
結構化字典 | class User(TypedDict): name: str; age: int |
Protocol |
靜態介面 | class SupportsClose(Protocol): def close(self) -> None: ... |
5. pyright 的型別推斷與報告
範例 1:自動推斷容器內型別
from typing import List
def first_item(items: List[int]) -> int:
# pyright 會推斷 items[0] 為 int
return items[0]
# 呼叫時若傳入 List[str],會得到錯誤訊息
first_item(["a", "b"])
# ──> Argument of type "List[str]" is not assignable to parameter "items" of type "List[int]"
範例 2:使用 TypedDict 定義 API 回傳結構
from typing import TypedDict, List
class Book(TypedDict):
title: str
author: str
pages: int
def get_book() -> Book:
# 正確回傳結構
return {"title": "Python 入門", "author": "Alice", "pages": 250}
def print_book(b: Book) -> None:
print(f"{b['title']} by {b['author']} ({b['pages']} 頁)")
print_book(get_book())
如果忘記 pages 欄位,pyright 會立刻提示缺少必填鍵:
Property "pages" is missing in type "{ title: str; author: str; }"
範例 3:Protocol 讓 duck-typing 受靜態檢查保護
from typing import Protocol
class SupportsWrite(Protocol):
def write(self, data: str) -> int: ...
def log(msg: str, dest: SupportsWrite) -> None:
dest.write(msg + "\n")
class FileWriter:
def __init__(self, path: str) -> None:
self.file = open(path, "a", encoding="utf-8")
def write(self, data: str) -> int:
return self.file.write(data)
log("開始執行", FileWriter("log.txt"))
即使 FileWriter 並未繼承 SupportsWrite,只要符合方法簽名,pyright 就會認可,避免因忘記實作 write 而產生的 runtime error。
範例 4:Literal 限制可接受的常數值
from typing import Literal
def set_mode(mode: Literal["debug", "release"]) -> None:
print(f"Mode set to {mode}")
set_mode("debug") # OK
set_mode("test") # pyright: Argument of type '"test"' is not assignable to parameter "mode"
範例 5:在 pyrightconfig.json 中自訂型別檔案路徑
假設專案有自訂的第三方 stub (.pyi) 檔案:
{
"typeCheckingMode": "strict",
"stubPath": "./stubs"
}
這樣 pyright 會自動載入 ./stubs 內的型別資訊,讓即使沒有原始碼的套件也能得到完整檢查。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解法 / Best Practice |
|---|---|---|
忽略 type: ignore |
隨意加 # type: ignore 會掩蓋真正的問題。 |
只在確定無法解決的第三方 stub 時使用,並加上說明。 |
過度使用 Any |
Any 會讓靜態檢查失效,等同於不寫型別。 |
盡量使用具體型別或 Protocol,必要時使用 cast()。 |
未設定 strict |
預設 basic 只檢查明顯錯誤,漏掉許多潛在問題。 |
在 CI/CD 中使用 typeCheckingMode: "strict",本機開發則可視需求調整。 |
| 忘記更新 stub | 第三方套件更新後,型別定義可能不相容。 | 使用 pip install -U 後,同步更新 pyrightconfig.json 中的 stubPath 或重新安裝 types- 套件。 |
未啟用 reportUnusedVariable |
未使用的變數會成為雜訊,降低可讀性。 | 在 pyrightconfig.json 開啟 reportUnusedVariable,配合 # noqa: F401 只在必要時抑制。 |
| 使用相對匯入時路徑錯誤 | pyright 可能找不到模組,報 Import "x" could not be resolved. |
在 pyrightconfig.json 加入 "venvPath": "./.venv" 或 "extraPaths": ["src"]。 |
最佳實踐總結
- 從
strict開始:即使在開發階段,嚴格模式可以提前發現問題。 - 把型別檢查納入 CI:在
github actions、GitLab CI中執行pyright,確保每次 PR 都通過。 - 分層使用
# type: ignore:只在不可避免的情況下使用,並加上註解說明原因。 - 善用
TypedDict、Protocol:取代過度使用dict或裸class,提升可讀性與檢查精度。 - 保持 stub 更新:對於常用的第三方套件(如
requests、pandas),安裝對應的types-套件或自行維護 stub。
實際應用場景
1. 大型微服務系統的 API 合約檢查
在微服務架構中,各服務之間透過 JSON/REST 互相呼叫。利用 TypedDict + pyright,開發者可以在 client SDK 中定義回傳結構,若服務端回傳欄位遺失或型別變更,SDK 使用者會在編譯時即收到警告,避免因不相容的 API 造成的 runtime error。
2. 數據科學專案的資料管道
資料清洗、特徵工程階段常常以 pandas.DataFrame 為主,型別不明確容易導致錯誤。結合 pandas-stubs(pip install pandas-stubs)與 pyright,可在 DataFrame 的列操作上得到型別推斷,例如:
import pandas as pd
from pandas import DataFrame
def filter_age(df: DataFrame) -> DataFrame:
# pyright 會推斷 df["age"] 為 Series[int]
return df[df["age"] > 18]
若不小心把 age 欄位寫成字串,pyright 會直接報錯。
3. CI/CD 中的型別安全門檻
在 GitHub Actions 工作流程中加入以下步驟,即可在 PR 合併前保證型別正確:
name: Python Type Check
on: [push, pull_request]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install -r requirements.txt
npm install -g pyright
- name: Run pyright
run: pyright --project .
只要 pyright 回傳非零狀態碼,CI 會失敗,確保所有型別問題在合併前被修正。
4. VS Code 中的即時協助
開發者在編寫函式時,pylance 會自動顯示 參數型別、回傳型別,以及 可能的錯誤。例如:
def calculate(a: int, b: int) -> float:
return a / b
當你在另一檔案呼叫 calculate("5", 2),pylance 會在編輯器底部顯示紅色波浪線,並在 hover 時提示:
Argument of type "str" is not assignable to parameter "a" of type "int".
這種即時回饋大幅降低除錯時間,尤其在多人協作的代碼基礎上更顯價值。
總結
- pyright 是一個高速、可自訂的型別檢查器,適合在任何編輯器或 CI 環境中使用。
- pylance 把 pyright 的引擎嵌入 VS Code,提供即時錯誤提示、智能補完與文件說明,提升開發效率。
- 透過 type hints、
TypedDict、Protocol、Literal等工具,開發者可以在 Python 中建立 明確的型別合約,減少 runtime 錯誤。 - 嚴格模式(strict)、CI 整合、適當的
# type: ignore使用,是確保型別安全的關鍵實踐。 - 在微服務、資料科學、CI/CD 等實務場景中,pyright/pylance 已被證明能提升程式碼品質、降低維護成本。
結語:型別提示不再是「額外負擔」,而是 提升程式碼可讀性、可維護性與安全性 的必備工具。只要配合 pyright/pylance,從現在開始把「寫對」變成「寫好」的日常習慣吧!