Python 型別提示:撰寫更安全、清晰的程式碼
你可能遇過某個 Python 函式會根據輸入回傳不同型別,或是某個變數中途改變型別。這些模糊地帶會導致執行時錯誤,而這些錯誤其實可以提前被發現。Python 型別提示能解決這個問題,它讓你標註預期的型別,使程式碼具備自我說明性,並讓靜態分析工具在執行前就捕捉到錯誤。
在本文中,我們將探討如何有效使用 Python 型別提示,從基礎標註到進階模式,以及它們如何協助你寫出更安全、更清晰的程式碼。
什麼是 Python 型別提示?
型別提示(也稱為型別標註)是 Python 3.5 引入的語法(PEP 484),允許你指定變數、函式參數與回傳值的預期型別。它們不會在執行時強制檢查,但會被 mypy、pyright 等靜態型別檢查器及 IDE 用來偵測型別相關的錯誤。
例如:
def greet(name: str) -> str:
return f"Hello, {name}"
這裡的 name: str 表示 name 應該是字串,而 -> str 表示函式回傳字串。
為什麼要使用型別提示?
- 提早發現錯誤: 靜態型別檢查器可以在你執行程式碼之前捕捉型別不符的情況。
- 提升可讀性: 型別提示就像內嵌文件,讓其他人(以及未來的你)更容易理解程式碼。
- 更好的 IDE 支援: 自動完成、重構與導覽變得更準確。
- 增強可維護性: 重構時,型別提示有助於確保你不會破壞既有的契約。
- 促進協作: 團隊可以清楚溝通預期的介面。
基礎型別標註
你可以標註變數、函式參數與回傳型別。
變數
age: int = 30
name: str = "Alice"
函式
def add(a: int, b: int) -> int:
return a + b
集合
對於串列、字典等,請使用 typing 模組(或在 Python 3.9+ 使用內建泛型):
from typing import List, Dict
def process(items: List[str]) -> Dict[str, int]:
return {item: len(item) for item in items}
在 Python 3.9+ 中,你可以直接使用 list[str] 和 dict[str, int]。
進階型別提示
隨著程式碼增長,你會遇到更複雜的情境。以下是一些進階功能。
Optional 與 Union
Optional[T] 是 Union[T, None] 的簡寫,表示值可以是 T 型別或 None。
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
# 回傳使用者名稱,若找不到則回傳 None
...
在 Python 3.10+ 中,你可以使用 | 運算子:str | None。
Callable
用於將函式作為參數:
from typing import Callable
def apply_func(func: Callable[[int], int], value: int) -> int:
return func(value)
泛型
使用型別變數建立可重複使用的元件:
from typing import TypeVar, List
T = TypeVar('T')
def first(items: List[T]) -> T:
return items[0]
TypedDict
用於具有固定鍵集合與值型別的字典:
from typing import TypedDict
class User(TypedDict):
name: str
age: int
def greet(user: User) -> str:
return f"Hello, {user['name']}"
使用靜態型別檢查器
型別提示只有在檢查時才有用。常見工具包括 mypy、pyright 和 pyre。讓我們看看如何使用 mypy。
- 安裝 mypy:
pip install mypy - 對你的程式碼執行:
mypy your_script.py - 修正回報的錯誤。
例如,給定以下程式碼:
def add(a: int, b: int) -> int:
return a + b
add("1", "2")
mypy 會回報:error: Argument 1 to "add" has incompatible type "str"; expected "int"。
你可以透過 mypy.ini 或 pyproject.toml 檔案設定 mypy 以執行更嚴格的檢查。
型別提示最佳實踐
- 保持一致: 為所有公開函式與方法加上標註。
- 謹慎使用
Any: 它會停用該值的型別檢查。 - 優先使用具體型別,而非
Any或object。 - 對於可能為 None 的值,使用
Optional。 - 利用
Protocol實現結構化子型別(兼具型別安全的鴨子型別)。 - 重構時保持型別提示最新。
- 在 CI 中執行型別檢查器,提早發現問題。
常見陷阱與避免方法
- 忽略型別錯誤: 不要只是讓 mypy 沉默;要理解並修正問題。
- 過度使用
Any: 這會使型別提示失去意義。 - 忘記標註回傳型別: 尤其是回傳 None 的函式。
- 使用可變預設引數搭配型別提示: 這是另一個 Python 陷阱,型別提示無法捕捉。
- 變更後未更新提示: 導致虛假的安全感。
型別提示實戰:一個小範例
考慮一個處理數字列表並回傳平均值的函式。沒有型別提示時,預期的型別並不清楚。
def average(numbers):
return sum(numbers) / len(numbers)
加上型別提示後:
from typing import List
def average(numbers: List[float]) -> float:
return sum(numbers) / len(numbers)
現在,如果有人傳入字串列表,mypy 會標記出來。
將型別提示整合到你的工作流程
要充分發揮型別提示的效益,請將它們整合到開發流程中:
- 逐步為現有程式碼庫加入型別提示。
- 設定 IDE 顯示型別錯誤。
- 在 CI 流程中加入型別檢查步驟。
- 使用 pre-commit 鉤子在提交前執行 mypy。
例如,一個簡單的 GitHub Actions 工作流程步驟:
- name: Type check
run: mypy .
常見問題
型別提示會影響執行效能嗎?
不會,型別提示在執行時會被忽略。它們儲存在 __annotations__ 中,但不會影響執行速度。
我可以在較舊的 Python 版本中使用型別提示嗎?
型別提示於 Python 3.5 引入。對於更舊的版本,你可以使用基於註解的標註(例如 # type: int),但建議升級到受支援的 Python 版本。
型別提示中 List 和 list 有什麼區別?
typing 中的 List 用於 Python 3.5-3.8。在 Python 3.9+ 中,你可以直接使用內建的 list。兩者在型別檢查上是等效的。
準備好提升你的程式碼品質了嗎?今天就從為一個函式加上型別提示開始,並執行 mypy 來看看好處。如需更多開發者工具,請查看我們的 JSON Formatter,輕鬆美化並驗證你的 JSON 資料。