Python 型別提示 (Type Hints) 與 Typing

Python 是一種動態型別 (Dynamically Typed) 的語言,這意味著變數的型別是在執行時期 (Runtime) 決定的,我們不需要(也無法強制)在宣告變數時指定型別。

然而,隨著專案規模變大,缺乏型別資訊會讓程式碼難以閱讀和維護。為了解決這個問題,Python 3.5 引入了型別提示 (Type Hints)

為什麼要使用型別提示?

  1. 增加可讀性:明確指出函式需要什麼參數、會回傳什麼,讓其他開發者(包括未來的自己)更容易看懂程式碼。
  2. IDE 支援:現代的編輯器 (如 VS Code, PyCharm) 可以利用型別資訊提供更好的自動完成 (Auto-completion) 和錯誤提示。
  3. 減少 Bug:搭配靜態型別檢查工具 (如 mypy),可以在執行前就發現型別錯誤。
Python 的型別提示不會在執行時期強制檢查型別。也就是說,即使你傳入了錯誤的型別,Python 直譯器通常不會報錯(除非該型別導致了後續的操作失敗)。型別提示主要是給開發者和工具看的。

基本語法

變數型別註釋

使用 變數: 型別 的語法:

name: str = "Alice"
age: int = 30
is_active: bool = True
height: float = 1.75

也可以只宣告不賦值(但通常不建議這樣做,除非在類別屬性中):

score: int
# ... 稍後再賦直
score = 100

函式型別註釋

我們可以標註 參數型別回傳值型別 (使用 ->):

def greeting(name: str) -> str:
    return "Hello, " + name

def add(x: int, y: int) -> int:
    return x + y

容器型別 (Collection Types)

這部分在 Python 3.9 前後有一些變化。

Python 3.9+ (推薦)

從 Python 3.9 開始,我們可以直接使用內建的容器類別 (如 list, dict, set) 來作為型別提示:

# 一個包含整數的列表
scores: list[int] = [10, 20, 30]

# 一個 key 為字串,value 為整數的字典
user_ages: dict[str, int] = {
    "Alice": 30,
    "Bob": 25
}

# 一個包含字串的集合
tags: set[str] = {"python", "coding"}

# 一個包含特定三個元素的元組
point: tuple[int, int, int] = (1, 2, 3)

Python 3.8 及更早版本

在舊版 Python 中,你需要從 typing 模組引入對應的大寫類別:

from typing import List, Dict, Set, Tuple

scores: List[int] = [10, 20, 30]
user_data: Dict[str, str] = {"name": "Alice"}

typing 模組的特殊型別

對於更複雜的型別需求,我們需要使用 typing 模組。

Any (任意型別)

Any 表示該變數可以是任何型別。這等於是告訴檢查工具:「這裡不要檢查型別」。

from typing import Any

def print_anything(value: Any) -> None:
    print(value)

print_anything(123)
print_anything("Hello")

Union (聯合型別)

當一個變數可能是多種型別之一時使用。

Python 3.10+ (推薦寫法,使用 |):

def process_id(user_id: int | str):
    print(f"Processing ID: {user_id}")

舊版寫法 (使用 Union):

from typing import Union

def process_id(user_id: Union[int, str]):
    pass

Optional (可選型別)

Optional[T] 等同於 Union[T, None],表示該值可能是型別 T,或是 None

from typing import Optional

def find_user(user_id: int) -> Optional[dict]:
    if user_id == 1:
        return {"name": "Admin"}
    return None

# Python 3.10+ 也可以寫成 dict | None

Callable (可呼叫物件)

當參數是需要傳入一個函式時使用。 語法:Callable[[參數型別1, 參數型別2], 回傳型別]

from typing import Callable

def apply_func(x: int, y: int, func: Callable[[int, int], int]) -> int:
    return func(x, y)

def multiply(a: int, b: int) -> int:
    return a * b

print(apply_func(5, 3, multiply))  # 15

Final (常數)

用來標示變數不應該被重新賦值(類似其他語言的 const)。

from typing import Final

PI: Final[float] = 3.14159

# PI = 3.14  # 靜態檢查工具會報錯

泛型 (Generics) 與 TypeVar

有時候我們希望函式的輸入型別和輸出型別是有關聯的,這時可以使用 TypeVar

from typing import TypeVar, list

T = TypeVar('T')

def get_first_element(items: list[T]) -> T:
    return items[0]

# 如果傳入 list[int],回傳就是 int
print(get_first_element([1, 2, 3])) 

# 如果傳入 list[str],回傳就是 str
print(get_first_element(["a", "b", "c"]))

靜態型別檢查工具 (mypy)

正如前面提到,Python 執行時不會檢查這些提示。要真正發揮型別提示的威力,你需要使用靜態檢查工具。最常用的是 mypy

安裝:

pip install mypy

執行檢查:

mypy script.py

如果有型別不符的地方,mypy 就會報錯,幫助你在寫程式的階段就抓出 Bug。