Python 課程 – 型別提示與靜態分析
主題:typing 模組(List、Dict、Optional、Union、Literal)
簡介
在 Python 3.5 之後,型別提示(type hints) 正式成為語言的一部份。雖然 Python 本身仍是動態型別語言,但透過 typing 模組,我們可以在程式碼中寫下「這個變數應該是什麼型別」,讓 IDE、型別檢查工具(如 mypy、pyright)在編譯前就找出潛在的錯誤。
對於 初學者,型別提示可以協助理解函式的輸入與輸出;對 中級開發者,則能在大型專案中提升可維護性、降低回歸錯誤的風險。因此,熟悉 typing 中常用的幾個泛型(generic)與特殊型別是非常重要的。
核心概念
1. 基本泛型:List 與 Dict
List、Dict 是最常見的容器型別。使用它們時,必須在方括號中指明元素的型別,例如 List[int] 表示「只會放整數的列表」。
from typing import List, Dict
def sum_numbers(nums: List[int]) -> int:
"""將整數列表相加,回傳總和。"""
return sum(nums)
def merge_maps(a: Dict[str, int], b: Dict[str, int]) -> Dict[str, int]:
"""合併兩個字典,若鍵相同則相加其值。"""
result = a.copy()
for k, v in b.items():
result[k] = result.get(k, 0) + v
return result
註解:在 Python 3.9 之後,也可以直接使用內建的
list[int]、dict[str, int],但在舊版或需要向後相容的情況下,仍建議使用typing.List、typing.Dict。
2. 可為 None 的型別:Optional
當一個變數可能是某個型別或是 None 時,使用 Optional[T](等同於 Union[T, None])最為直觀。
from typing import Optional
def find_user(name: str) -> Optional[dict]:
"""模擬資料庫查詢,找不到時回傳 None。"""
fake_db = {"alice": {"age": 30}, "bob": {"age": 25}}
return fake_db.get(name) # 可能是 dict,也可能是 None
呼叫端可以利用型別檢查工具提醒必須先做 None 判斷:
user = find_user("charlie")
if user is None:
print("使用者不存在")
else:
print(f"年齡是 {user['age']}")
3. 多型別選擇:Union
Union 允許同一個變數接受多種型別。在 API 設計時常會看到「參數可以是字串或是整數」的情況。
from typing import Union
def repeat(value: Union[str, int], times: int) -> str:
"""
若 value 為字串,直接重複;
若為整數,先轉成字串再重複。
"""
if isinstance(value, int):
value = str(value)
return value * times
Union 也常與 Optional 結合(例如 Union[int, None]),但直接寫成 Optional[int] 更簡潔。
4. 常量限定:Literal
Literal 用於限制變數只能是特定的常量值,常見於函式的參數或設定檔的欄位。
from typing import Literal
def set_log_level(level: Literal["DEBUG", "INFO", "WARN", "ERROR"]) -> None:
"""只允許四種固定的日誌等級。"""
print(f"Log level set to {level}")
# 正確使用
set_log_level("INFO")
# 錯誤使用(型別檢查器會警告)
# set_log_level("TRACE") # -> mypy: Argument 1 to "set_log_level" has incompatible type "Literal['TRACE']"
Literal 能夠大幅減少「打錯字」或「傳入不支援的字串」的問題,尤其在與外部套件(如 click、argparse)結合時非常有用。
5. 結合使用的範例
以下示範一個 設定檔解析器,同時運用了 List、Dict、Optional、Union、Literal:
from typing import List, Dict, Optional, Union, Literal
ConfigValue = Union[int, str, bool, None]
LogLevel = Literal["DEBUG", "INFO", "WARN", "ERROR"]
class Config:
def __init__(self, data: Dict[str, ConfigValue]) -> None:
self._data = data
def get(self, key: str, default: Optional[ConfigValue] = None) -> ConfigValue:
"""取得設定值,若不存在回傳 default。"""
return self._data.get(key, default)
def get_int_list(self, key: str) -> List[int]:
"""期望取得由逗號分隔的整數字串,轉換成 List[int]。"""
raw = self.get(key, "")
if not isinstance(raw, str):
raise TypeError(f"{key} 必須是字串")
return [int(x) for x in raw.split(",") if x]
def set_log_level(self, level: LogLevel) -> None:
"""設定日誌等級,只接受四種字串。"""
self._data["log_level"] = level
這段程式碼展示了:
- 容器型別 (
List[int]、Dict[str, ConfigValue]) - 可為
None的參數 (Optional[ConfigValue]) - 多型別 (
Union[int, str, bool, None]) - 字面量限定 (
Literal["DEBUG", ...])
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記導入 typing |
直接寫 List[int] 會導致 NameError。 |
在檔案最上方 from typing import List, Dict, Optional, Union, Literal。 |
| 使用可變容器作為預設值 | def foo(arg: List[int] = []): 會共用同一個列表。 |
使用 None 搭配 Optional[List[int]],在函式內部再初始化:def foo(arg: Optional[List[int]] = None): arg = arg or [] |
Union 與 Optional 重複 |
Optional[int] 已等同於 Union[int, None],寫成 Union[int, None] 會冗長。 |
直接使用 Optional。 |
Literal 僅在 Python 3.8+ 有效 |
低版本會出現 ImportError。 |
若需要相容舊版,可改用 Enum 或自行實作驗證。 |
忽略 type: ignore 的濫用 |
為了「暫時」跳過錯誤而大量加上 # type: ignore,會失去型別檢查的好處。 |
只在確定無法避免的情況下使用,並加上說明註解。 |
最佳實踐:
- 盡量在公共 API(函式、類別)上加上完整的型別提示,內部實作可視情況逐步補上。
- 使用
mypy --strict進行嚴格檢查,能提前發現不一致的型別。 - 配合 IDE(如 VS Code、PyCharm),即時看到型別提示與錯誤,提升開發效率。
- 對外部資料(JSON、環境變數)進行驗證,不要直接假設它們符合型別;可結合
pydantic、dataclasses+typing。 - 保持型別別名(type alias),如上例的
ConfigValue,有助於程式碼可讀性與維護。
實際應用場景
| 場景 | 為何需要型別提示 | 相關 typing 元件 |
|---|---|---|
| REST API 端點 | 請求與回應的 JSON 結構固定,型別提示可以防止欄位遺漏或型別錯誤。 | TypedDict、Literal、Optional |
| 資料庫 ORM | ORM 層的模型屬性對應資料表欄位,使用 Optional 表示可為 NULL,使用 Union 處理多型別欄位。 |
Optional、Union |
| 命令列工具 | click/argparse 解析參數時,可用 Literal 限制可接受的子命令或選項。 |
Literal |
| 大型機器學習 pipeline | 前處理、模型、後處理各自的輸入/輸出型別需要明確定義,以免在 pipeline 組合時出錯。 | List、Dict、Union |
| 多人協作的程式庫 | 當多位開發者共同維護同一套 API 時,型別提示提供了「自我說明」的文件,減少溝通成本。 | 全部(List、Dict、Optional、Union、Literal) |
總結
typing模組提供 List、Dict、Optional、Union、Literal 等工具,讓 Python 程式碼在保持彈性的同時,具備更好的可讀性與安全性。- 正確使用 容器型別(
List、Dict)可以清楚表達集合內元素的型別;Optional與Union處理「可能是多種型別」的情況;Literal則用於限定「只能是特定常量」的參數。 - 避免常見陷阱(如可變預設值、重複使用
Union與Optional),並遵循最佳實踐(嚴格檢查、型別別名、配合 IDE)能讓型別提示發揮最大效益。 - 在 API、CLI、資料庫、機器學習 pipeline 等實務情境中,合理的型別提示不僅提升開發效率,還能在團隊協作與長期維護上帶來顯著好處。
**掌握型別提示,就是為自己的程式碼加上一層堅固的防護牆。**未來隨著 Python 靜態分析工具的持續進化,善用
typing會成為每位 Python 開發者的必備技能。祝你寫程式寫得更快、更安全!