為什麼需要型別提示? #
Python 一直以來都是「動態型別」語言,這代表你可以這樣寫:
def add(a, b):
return a + b
result = add(5, 3) # OK
result = add("5", "3") # 也 OK,但結果是 "53" 字串!
result = add(5, "3") # 執行時才會炸裂 TypeError
這種靈活性雖然寫起來很爽快,但當專案變大、團隊協作時,就會遇到以下麻煩:
- Debug 很痛苦:執行時才發現型別錯誤,浪費時間
- 不知道該傳什麼參數:看到別人的函數,完全猜不透要傳什麼型別
- IDE 自動補全失靈:不知道變數是什麼型別,IDE 沒法給提示
- 重構很恐怖:改了一個函數,不知道哪裡會炸
這時候 typing 模組就派上用場了!它讓你可以在 Python 裡加上「型別提示」(Type Hints),讓程式更安全、更好維護。
型別提示的好處 #
- 提前發現錯誤:用
mypy這類工具可以在執行前就檢查出型別錯誤 - 自動補全超強大:VSCode、PyCharm 等 IDE 會根據型別提示給你精準的自動補全
- 文檔自動生成:型別提示就是最好的文檔,一看就懂
- 重構更安心:改了函數簽名,IDE 會自動標出所有錯誤的呼叫處
- 程式碼更易讀:一眼就知道變數、參數、回傳值的型別
基本用法 #
1. 基本型別標註 #
# 變數型別標註
name: str = "拍拍君"
age: int = 18
height: float = 175.5
is_student: bool = True
# 函數參數與回傳值標註
def greet(name: str) -> str:
return f"Hello, {name}!"
def add(a: int, b: int) -> int:
return a + b
2. 容器型別(List、Dict、Tuple) #
from typing import List, Dict, Tuple, Set
# List:列表裡的元素都是同一種型別
scores: List[int] = [90, 85, 88]
names: List[str] = ["Alice", "Bob"]
# Dict:字典的 key 和 value 型別
user: Dict[str, int] = {"age": 18, "score": 90}
config: Dict[str, str] = {"host": "localhost", "port": "8080"}
# Tuple:元組可以指定每個位置的型別
point: Tuple[int, int] = (10, 20)
person: Tuple[str, int, bool] = ("拍拍君", 18, True)
# Set:集合
tags: Set[str] = {"python", "typing", "tutorial"}
3. Optional 與 None #
from typing import Optional
# Optional[str] 表示可能是 str 或 None
def find_user(user_id: int) -> Optional[str]:
if user_id == 1:
return "拍拍君"
return None
# 等同於
def find_user(user_id: int) -> str | None:
...
4. Union:多種型別其中之一 #
from typing import Union
# 參數可以是 int 或 str
def process(value: Union[int, str]) -> str:
return str(value)
# Python 3.10+ 可以用 | 符號(更簡潔!)
def process(value: int | str) -> str:
return str(value)
進階用法 #
1. 泛型函數:TypeVar #
當你想寫一個「通用」的函數,輸入什麼型別就回傳什麼型別:
from typing import TypeVar, List
T = TypeVar('T')
def first_element(items: List[T]) -> T:
return items[0]
# 自動推導型別
num = first_element([1, 2, 3]) # num 是 int
text = first_element(["a", "b"]) # text 是 str
2. Callable:函數型別 #
from typing import Callable
# Callable[[參數型別], 回傳型別]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
def add(x: int, y: int) -> int:
return x + y
result = apply(add, 5, 3) # 結果是 8
3. Protocol:結構化型別(Duck Typing) #
不用繼承也能定義「契約」:
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None:
...
class Circle:
def draw(self) -> None:
print("Drawing circle")
class Square:
def draw(self) -> None:
print("Drawing square")
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # OK!
render(Square()) # 也 OK!
只要有 draw() 方法就符合 Drawable 的契約,不用繼承!
4. Generic:自訂泛型類別 #
from typing import Generic, TypeVar
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
def get(self) -> T:
return self.value
int_box = Box(42) # Box[int]
str_box = Box("hello") # Box[str]
5. Literal:限定特定值 #
from typing import Literal
def set_color(color: Literal["red", "green", "blue"]) -> None:
print(f"Color set to {color}")
set_color("red") # OK
set_color("yellow") # mypy 會警告錯誤!
6. TypedDict:字典型別定義 #
from typing import TypedDict
class User(TypedDict):
name: str
age: int
email: str
user: User = {
"name": "拍拍君",
"age": 18,
"email": "patpat@example.com"
}
# IDE 會自動補全 user["name"] 等 key!
7. Annotated:加上額外資訊 #
from typing import Annotated
# 可以加上驗證規則、單位等資訊
UserId = Annotated[int, "Must be positive"]
Distance = Annotated[float, "Unit: meters"]
def get_user(user_id: UserId) -> str:
...
型別檢查工具:mypy #
型別提示只是「提示」,Python 執行時不會強制檢查。要真正檢查型別,需要用 mypy:
pip install mypy
然後檢查你的程式碼:
mypy your_script.py
如果有型別錯誤,mypy 會告訴你哪裡出問題:
def add(a: int, b: int) -> int:
return a + b
result = add(5, "3") # mypy 會報錯:Argument 2 has incompatible type "str"
實戰範例:API 回應處理 #
from typing import TypedDict, Optional, List
class ApiResponse(TypedDict):
status: int
data: Optional[List[dict]]
error: Optional[str]
def fetch_users() -> ApiResponse:
# 模擬 API 呼叫
return {
"status": 200,
"data": [{"id": 1, "name": "拍拍君"}],
"error": None
}
response = fetch_users()
if response["status"] == 200 and response["data"]:
for user in response["data"]:
print(user["name"]) # IDE 知道這是 dict!
小技巧 #
- 不用每個變數都加型別提示,只在「容易混淆」或「公開 API」的地方加
- 善用
# type: ignore來忽略特定行的型別檢查(但不要濫用!) - 搭配 VSCode 的 Pylance 可以即時顯示型別錯誤
- Python 3.10+ 推薦用
|取代Union,更簡潔 - 可以在
pyproject.toml或mypy.ini設定 mypy 的檢查規則
結語 #
雖然 Python 不強制型別檢查,但加上 typing 提示後,可以讓你的程式碼更安全、更好維護,尤其在大型專案或團隊協作時超級��感!拍拍君自己現在寫 Python 都會盡量加上型別提示,尤其是函數的參數和回傳值,這樣過幾個月回來看也不會忘記當初在寫什麼。
下次寫 Python 時,不妨試試加上型別提示,讓你的 IDE 變得更聰明、bug 變得更少吧!