快轉到主要內容
  1. 教學文章/

Python Typing:讓你的程式碼更安全、更好維護

·4 分鐘· loading · loading · ·
Python Typing Type Hints
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Python 學習 - 本文屬於一個選集。
§ 14: 本文

為什麼需要型別提示?
#

Python 一直以來都是「動態型別」語言,這代表你可以這樣寫:

def add(a, b):
    return a + b

result = add(5, 3)        # OK
result = add("5", "3")    # 也 OK,但結果是 "53" 字串!
result = add(5, "3")      # 執行時才會炸裂 TypeError

這種靈活性雖然寫起來很爽快,但當專案變大、團隊協作時,就會遇到以下麻煩:

  1. Debug 很痛苦:執行時才發現型別錯誤,浪費時間
  2. 不知道該傳什麼參數:看到別人的函數,完全猜不透要傳什麼型別
  3. IDE 自動補全失靈:不知道變數是什麼型別,IDE 沒法給提示
  4. 重構很恐怖:改了一個函數,不知道哪裡會炸

這時候 typing 模組就派上用場了!它讓你可以在 Python 裡加上「型別提示」(Type Hints),讓程式更安全、更好維護。

型別提示的好處
#

  1. 提前發現錯誤:用 mypy 這類工具可以在執行前就檢查出型別錯誤
  2. 自動補全超強大:VSCode、PyCharm 等 IDE 會根據型別提示給你精準的自動補全
  3. 文檔自動生成:型別提示就是最好的文檔,一看就懂
  4. 重構更安心:改了函數簽名,IDE 會自動標出所有錯誤的呼叫處
  5. 程式碼更易讀:一眼就知道變數、參數、回傳值的型別

基本用法
#

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.tomlmypy.ini 設定 mypy 的檢查規則

結語
#

雖然 Python 不強制型別檢查,但加上 typing 提示後,可以讓你的程式碼更安全、更好維護,尤其在大型專案或團隊協作時超級��感!拍拍君自己現在寫 Python 都會盡量加上型別提示,尤其是函數的參數和回傳值,這樣過幾個月回來看也不會忘記當初在寫什麼。

下次寫 Python 時,不妨試試加上型別提示,讓你的 IDE 變得更聰明、bug 變得更少吧!

Python 學習 - 本文屬於一個選集。
§ 14: 本文

相關文章

Python 資料驗證小幫手:Pydantic
·4 分鐘· loading · loading
Python Pydantic Data Validation
Python: 我需要進度條! tqdm
·3 分鐘· loading · loading
Python Tqdm Data Science
科學計算:數值積分
·5 分鐘· loading · loading
Python Numpy Scipy Numerical Methods Numerical Integral
管理秘密環境變數 python-dotenv
·1 分鐘· loading · loading
Python Dotenv
讓你的終端機華麗變身:Rich 套件教學
·2 分鐘· loading · loading
Python Rich Cli
開發的好習慣 Unit Test
·5 分鐘· loading · loading
Python Pytest Ci Unittest