本文 AI 產出,尚未審核

Python 課程 – 型別提示與靜態分析

主題:typing 模組(List、Dict、Optional、Union、Literal)


簡介

在 Python 3.5 之後,型別提示(type hints) 正式成為語言的一部份。雖然 Python 本身仍是動態型別語言,但透過 typing 模組,我們可以在程式碼中寫下「這個變數應該是什麼型別」,讓 IDE、型別檢查工具(如 mypypyright)在編譯前就找出潛在的錯誤。

對於 初學者,型別提示可以協助理解函式的輸入與輸出;對 中級開發者,則能在大型專案中提升可維護性、降低回歸錯誤的風險。因此,熟悉 typing 中常用的幾個泛型(generic)與特殊型別是非常重要的。


核心概念

1. 基本泛型:ListDict

ListDict 是最常見的容器型別。使用它們時,必須在方括號中指明元素的型別,例如 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.Listtyping.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 能夠大幅減少「打錯字」或「傳入不支援的字串」的問題,尤其在與外部套件(如 clickargparse)結合時非常有用。


5. 結合使用的範例

以下示範一個 設定檔解析器,同時運用了 ListDictOptionalUnionLiteral

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 []
UnionOptional 重複 Optional[int] 已等同於 Union[int, None],寫成 Union[int, None] 會冗長。 直接使用 Optional
Literal 僅在 Python 3.8+ 有效 低版本會出現 ImportError 若需要相容舊版,可改用 Enum 或自行實作驗證。
忽略 type: ignore 的濫用 為了「暫時」跳過錯誤而大量加上 # type: ignore,會失去型別檢查的好處。 只在確定無法避免的情況下使用,並加上說明註解。

最佳實踐

  1. 盡量在公共 API(函式、類別)上加上完整的型別提示,內部實作可視情況逐步補上。
  2. 使用 mypy --strict 進行嚴格檢查,能提前發現不一致的型別。
  3. 配合 IDE(如 VS Code、PyCharm),即時看到型別提示與錯誤,提升開發效率。
  4. 對外部資料(JSON、環境變數)進行驗證,不要直接假設它們符合型別;可結合 pydanticdataclasses + typing
  5. 保持型別別名(type alias),如上例的 ConfigValue,有助於程式碼可讀性與維護。

實際應用場景

場景 為何需要型別提示 相關 typing 元件
REST API 端點 請求與回應的 JSON 結構固定,型別提示可以防止欄位遺漏或型別錯誤。 TypedDictLiteralOptional
資料庫 ORM ORM 層的模型屬性對應資料表欄位,使用 Optional 表示可為 NULL,使用 Union 處理多型別欄位。 OptionalUnion
命令列工具 click/argparse 解析參數時,可用 Literal 限制可接受的子命令或選項。 Literal
大型機器學習 pipeline 前處理、模型、後處理各自的輸入/輸出型別需要明確定義,以免在 pipeline 組合時出錯。 ListDictUnion
多人協作的程式庫 當多位開發者共同維護同一套 API 時,型別提示提供了「自我說明」的文件,減少溝通成本。 全部(ListDictOptionalUnionLiteral

總結

  • typing 模組提供 List、Dict、Optional、Union、Literal 等工具,讓 Python 程式碼在保持彈性的同時,具備更好的可讀性與安全性。
  • 正確使用 容器型別ListDict)可以清楚表達集合內元素的型別;OptionalUnion 處理「可能是多種型別」的情況;Literal 則用於限定「只能是特定常量」的參數。
  • 避免常見陷阱(如可變預設值、重複使用 UnionOptional),並遵循最佳實踐(嚴格檢查、型別別名、配合 IDE)能讓型別提示發揮最大效益。
  • API、CLI、資料庫、機器學習 pipeline 等實務情境中,合理的型別提示不僅提升開發效率,還能在團隊協作與長期維護上帶來顯著好處。

**掌握型別提示,就是為自己的程式碼加上一層堅固的防護牆。**未來隨著 Python 靜態分析工具的持續進化,善用 typing 會成為每位 Python 開發者的必備技能。祝你寫程式寫得更快、更安全!