Python 課程 – 函式(Functions)
主題:型別提示(type hints)
簡介
在 Python 中,函式的參數與回傳值本質上是「動態」的,執行時才決定實際型別。這種彈性讓語言非常友好,但在大型專案或多人協作時,缺乏明確的型別資訊往往會導致 程式錯誤難以追蹤、IDE 的自動補完失效,甚至降低程式碼可讀性。
自 Python 3.5 起加入的 型別提示(type hints),提供了一套靜態型別註記的語法。它不會改變程式的執行行為,卻能讓開發者在編寫階段就得到類似編譯語言的好處:提前發現錯誤、提升 IDE 智慧提示、生成更清晰的文件。對於從初學者晉升為中級開發者的你,掌握型別提示是提升程式品質的重要一步。
核心概念
1️⃣ 基本語法:-> 與 :
- 參數型別:在參數名稱後加上冒號
:,接著寫型別。 - 回傳型別:在函式簽名最後加上
->,再寫回傳值的型別。
def add(a: int, b: int) -> int:
"""將兩個整數相加,回傳結果為整數。"""
return a + b
重點:即使加上型別提示,Python 仍會接受任何型別的實參。若想在執行時強制檢查,需要額外使用工具(如
typeguard)。
2️⃣ 常用型別與 typing 模組
內建型別(int、str、float、bool)之外,typing 提供了許多 抽象型別,讓我們能描述更複雜的結構。
| 型別 | 說明 |
|---|---|
List[T] |
同步列表,元素型別為 T |
Tuple[T1, T2, ...] |
固定長度的元組 |
Dict[K, V] |
字典,鍵型別 K、值型別 V |
Optional[T] |
T 或 None(等同於 Union[T, None]) |
Union[A, B] |
可能是 A 或 B |
Callable[[A, B], R] |
接受 A, B 參數、回傳 R 的函式 |
Any |
任意型別,等同於不加提示 |
from typing import List, Tuple, Dict, Optional, Union, Callable, Any
def process_data(data: List[int]) -> Tuple[int, int]:
"""回傳 (最小值, 最大值)。"""
return min(data), max(data)
def greet(name: Optional[str] = None) -> str:
"""若未提供 name,回傳預設問候。"""
return f"Hello, {name or 'Guest'}!"
def apply(func: Callable[[int, int], int], x: int, y: int) -> int:
"""將傳入的二元函式套用於 x, y。"""
return func(x, y)
def echo(value: Any) -> Any:
"""回傳相同的值,型別不做限制。"""
return value
3️⃣ 泛型(Generics)
當函式需要接受 任意型別的容器,卻仍想保留元素型別資訊,可使用 TypeVar 定義 泛型變數。
from typing import TypeVar, Sequence
T = TypeVar('T') # 任意型別
def first_item(seq: Sequence[T]) -> T:
"""回傳序列的第一個元素,保持原始型別。"""
return seq[0]
# 使用範例
num = first_item([1, 2, 3]) # 推斷為 int
word = first_item(("a", "b", "c")) # 推斷為 str
技巧:在大型程式庫中,透過泛型可以讓 API 的型別資訊更精準,IDE 也能正確推斷返回值型別。
4️⃣ 延遲評估的字串型別(PEP 563)
Python 3.7 之後,可在檔案最上方加入 from __future__ import annotations,讓所有型別提示在執行時以 字串 形式保存,避免循環引用或前置宣告的問題。
from __future__ import annotations
from typing import List
class Node:
def __init__(self, value: int, children: List[Node] | None = None):
self.value = value
self.children = children or []
這樣寫的好處是 型別提示不會在模組載入時立即求值,提升效能並簡化相互依賴的類別定義。
5️⃣ 靜態檢查工具
型別提示本身不會執行檢查,必須結合 靜態分析工具(如 mypy、pyright、pylint)才能在開發階段發現不匹配的情況。
# 安裝 mypy
pip install mypy
# 檢查 my_module.py
mypy my_module.py
實務建議:在 CI/CD 流程中加入
mypy --strict,確保所有 PR 都符合型別規範。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記匯入 typing |
直接使用 List、Dict 等會報 NameError。 |
在檔案開頭 from typing import List, Dict, ...。 |
| 使用可變預設值 | def f(arg: List[int] = []): 會共用同一個列表。 |
改為 `def f(arg: List[int] |
過度使用 Any |
失去型別提示的意義,錯誤不易捕捉。 | 盡量使用具體型別或 Protocol 取代 Any。 |
忽略 Optional |
把可能為 None 的參數寫成 str,導致 NoneType 錯誤。 |
明確寫 Optional[str],或在程式內檢查 if value is None:。 |
| 循環引用 | 兩個類別相互引用時,型別提示會導致 NameError。 |
使用 前置字串(PEP 563)或 typing.TYPE_CHECKING 條件匯入。 |
最佳實踐
- 從小範圍開始:先為公開 API(函式、類別方法)加上型別提示,再逐步擴展到內部實作。
- 遵守
PEP 484:遵循官方型別提示規範,確保程式碼在不同工具間的相容性。 - 使用
Literal:對於只能接受固定值的參數,用Literal提高可讀性與檢查精度。
from typing import Literal
def set_mode(mode: Literal["r", "w", "a"]) -> None:
"""只能接受 'r', 'w', 'a' 三種模式。"""
print(f"Mode set to {mode}")
- 加入文件字串:在函式說明中同時寫下型別說明,讓使用者即使不看型別提示也能了解參數需求。
- 在 CI 中加入型別檢查:將
mypy --strict或pyright作為測試的一部份,防止型別回退。
實際應用場景
1️⃣ 大型 Web API 專案
在 FastAPI、Django REST Framework 等框架中,型別提示直接影響自動產生的文件(OpenAPI、Swagger)。例如:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
tags: list[str] = []
@app.post("/items/")
def create_item(item: Item) -> Item:
"""回傳建立後的商品資訊。"""
return item
FastAPI 會根據 Item 的型別自動產生 JSON Schema,讓前端開發者即時取得正確的欄位資訊。
2️⃣ 數據分析與機器學習管線
在 pandas、NumPy、scikit-learn 等庫的組合使用中,型別提示能協助辨識 DataFrame、Series 的欄位型別,減少因欄位名稱拼寫錯誤導致的 KeyError。
import pandas as pd
from typing import Literal
def filter_by_category(df: pd.DataFrame, column: str, category: Literal["A", "B", "C"]) -> pd.DataFrame:
"""依照特定類別過濾 DataFrame。"""
return df[df[column] == category]
3️⃣ 內部工具與自動化腳本
即使是小型腳本,加入型別提示也能提升維護性。當同事接手時,函式簽名即是使用說明,減少溝通成本。
def copy_file(src: str, dst: str, overwrite: bool = False) -> None:
"""將檔案從 src 複製到 dst,若 overwrite 為 True 則允許覆寫。"""
import shutil, os
if not overwrite and os.path.exists(dst):
raise FileExistsError(f"{dst} already exists")
shutil.copy2(src, dst)
總結
型別提示是 Python 靜態型別檢查的核心工具,它不會改變程式的執行結果,但能在開發階段提供:
- 更好的可讀性 – 函式簽名即是使用說明。
- 提前捕捉錯誤 – 配合
mypy、pyright等工具,減少執行時例外。 - IDE 智慧提示 – 自動補完與跳轉更精準。
- 文件自動生成 – 如 FastAPI、Pydantic 可直接產生 API 規格。
對於從 初學者 到 中級開發者,建議逐步將型別提示納入日常開發流程:先從公共函式開始,使用 typing 中的基本型別,然後慢慢引入泛型、Literal、Protocol 等進階概念,最後在 CI 中加入嚴格的型別檢查。如此一來,你的程式碼將更易維護、更具可擴充性,也能在團隊合作時展現專業水準。祝你在 Python 的型別世界裡寫出更安全、更清晰的程式!