當函式的參數或返回值可能是多種型別時,我們需要使用 OptionalUnion 或泛型來表達。這些工具讓型別提示更加精確。

Optional

Optional[T] 表示值可能是 T 型別,也可能是 None

基本用法

 1from typing import Optional
 2
 3def get_current_branch() -> Optional[str]:
 4    """
 5    獲取當前分支名稱
 6
 7    Returns:
 8        str | None: 分支名稱,如果無法獲取則返回 None
 9    """
10    success, output = run_git_command(["branch", "--show-current"])
11    return output if success and output else None

Optional 等同於 Union[T, None]

 1from typing import Optional, Union
 2
 3# 這兩種寫法等價
 4def find_user(id: int) -> Optional[User]:
 5    ...
 6
 7def find_user(id: int) -> Union[User, None]:
 8    ...
 9
10# Python 3.10+ 可以使用 | 語法
11def find_user(id: int) -> User | None:
12    ...

實際範例:配置載入

來自 .claude/lib/config_loader.py

 1from typing import Optional
 2
 3# 模組級快取變數
 4_agents_config_cache: Optional[dict] = None
 5_quality_rules_cache: Optional[dict] = None
 6
 7def load_agents_config() -> dict:
 8    """載入代理人配置"""
 9    global _agents_config_cache
10
11    # 快取為 None 表示尚未載入
12    if _agents_config_cache is None:
13        try:
14            _agents_config_cache = load_config("agents")
15        except FileNotFoundError:
16            _agents_config_cache = _get_default_agents_config()
17
18    return _agents_config_cache

Union

Union[A, B, C] 表示值可能是 A、B 或 C 型別。

基本用法

 1from typing import Union
 2
 3def process_input(data: Union[str, bytes]) -> str:
 4    """處理字串或位元組輸入"""
 5    if isinstance(data, bytes):
 6        return data.decode("utf-8")
 7    return data
 8
 9# Python 3.10+ 語法
10def process_input(data: str | bytes) -> str:
11    ...

常見應用

 1from typing import Union, List
 2
 3# 接受單一值或列表
 4def ensure_list(value: Union[str, List[str]]) -> List[str]:
 5    if isinstance(value, str):
 6        return [value]
 7    return value
 8
 9# 返回不同型別
10def parse_value(text: str) -> Union[int, float, str]:
11    try:
12        return int(text)
13    except ValueError:
14        try:
15            return float(text)
16        except ValueError:
17            return text

泛型(Generic)

泛型讓你建立可以與多種型別一起使用的類別和函式。

TypeVar

 1from typing import TypeVar, List
 2
 3T = TypeVar("T")
 4
 5def first_element(items: List[T]) -> T:
 6    """返回列表的第一個元素"""
 7    return items[0]
 8
 9# 使用時會推斷型別
10names = ["Alice", "Bob"]
11first_name = first_element(names)  # 推斷為 str
12
13numbers = [1, 2, 3]
14first_num = first_element(numbers)  # 推斷為 int

有限制的 TypeVar

1from typing import TypeVar
2
3# 只能是 str 或 bytes
4StrOrBytes = TypeVar("StrOrBytes", str, bytes)
5
6def process(data: StrOrBytes) -> StrOrBytes:
7    return data.strip()  # str 和 bytes 都有 strip()

實際範例:Hook 輸出建立

來自 .claude/lib/hook_io.py

 1from typing import Any, Optional
 2
 3def create_pretooluse_output(
 4    decision: str,
 5    reason: str,
 6    user_prompt: Optional[str] = None,
 7    system_message: Optional[str] = None,
 8    suppress_output: bool = False
 9) -> dict:
10    """建立 PreToolUse Hook 輸出格式"""
11
12    # 使用 dict[str, Any] 表示值可以是任意型別
13    output: dict[str, Any] = {
14        "hookSpecificOutput": {
15            "hookEventName": "PreToolUse",
16            "permissionDecision": decision,
17            "permissionDecisionReason": reason
18        }
19    }
20
21    # Optional 參數的處理
22    if user_prompt:
23        output["hookSpecificOutput"]["userPrompt"] = user_prompt
24
25    if system_message:
26        output["systemMessage"] = system_message
27
28    if suppress_output:
29        output["suppressOutput"] = True
30
31    return output

Any

Any 表示任意型別,相當於關閉型別檢查。

 1from typing import Any
 2
 3def log_value(value: Any) -> None:
 4    """記錄任意型別的值"""
 5    print(f"Value: {value}")
 6
 7# 字典值為任意型別
 8config: dict[str, Any] = {
 9    "name": "test",      # str
10    "count": 10,         # int
11    "enabled": True,     # bool
12    "items": [1, 2, 3]   # list
13}

何時使用 Any?

  • 處理動態資料(如 JSON)
  • 與外部 API 互動
  • 型別過於複雜難以表達

注意:過度使用 Any 會降低型別檢查的效果。

Callable

表示可呼叫物件(函式、方法、lambda)。

 1from typing import Callable
 2
 3def apply_operation(
 4    value: int,
 5    operation: Callable[[int], int]
 6) -> int:
 7    """對值應用操作"""
 8    return operation(value)
 9
10# 使用
11result = apply_operation(5, lambda x: x * 2)  # 10

Python 3.9+ 語法

從 Python 3.9 開始,可以直接使用內建型別:

1# Python 3.9+
2def process(items: list[str]) -> dict[str, int]:
3    ...
4
5# Python 3.8 及之前需要導入
6from typing import List, Dict
7def process(items: List[str]) -> Dict[str, int]:
8    ...

從 Python 3.10 開始,可以使用 | 語法:

1# Python 3.10+
2def find(id: int) -> User | None:
3    ...
4
5# Python 3.9 及之前
6from typing import Optional
7def find(id: int) -> Optional[User]:
8    ...

常見模式

可選參數

1def setup_logging(
2    name: str,
3    level: Optional[int] = None,    # 可以是 int 或 None
4    path: Optional[str] = None      # 可以是 str 或 None
5) -> Logger:
6    if level is None:
7        level = logging.INFO
8    ...

返回值可能為空

1def find_config(name: str) -> Optional[dict]:
2    """找不到配置時返回 None"""
3    path = get_config_path(name)
4    if not path.exists():
5        return None
6    return load_config(path)

字典值型別不確定

1from typing import Any
2
3def parse_json(text: str) -> dict[str, Any]:
4    """JSON 值可能是任意型別"""
5    return json.loads(text)

思考題

  1. Optional[str]str | None 有什麼區別?
  2. 什麼時候應該用 Any?什麼時候應該避免?
  3. 如何為一個接受任意可迭代物件的函式添加型別提示?

實作練習

  1. 為以下函式添加型別提示:

    1def get_value(data, key, default=None):
    2    return data.get(key, default)
  2. 寫一個泛型函式,返回列表中的最大和最小值

  3. 為一個回調函式參數添加 Callable 型別提示

延伸閱讀(進階系列)


上一章:Type Hints 基礎 下一章:Dataclass 資料結構