9.1 类型注解基础
目标
- 读懂参数、返回值、变量的注解写法。
- 读懂
list[str]、dict[str, int]、tuple[int, ...]、X | None、Any。 - 理解注解不影响运行:解释器不检查,检查由 mypy、pyright 等工具或 IDE 完成。
核心概念
语法
def get_weather(city: str, unit: str = "C") -> str: # 参数: 类型 = 默认值;-> 返回类型
return f"{city}:22°{unit}"
max_turns: int = 5 # 变量注解
messages: list[dict[str, str]] = [] # 嵌套泛型
provider: str | None = None # 可能为 None
print(get_weather("上海"))
print(get_weather.__annotations__) # 注解保存在 __annotations__ 中常见类型写法
| 写法 | 含义 | 旧写法(老代码中常见) |
|---|---|---|
list[str] | 元素为 str 的列表 | List[str](from typing import List) |
dict[str, int] | 键 str、值 int 的字典 | Dict[str, int] |
tuple[int, str] | 固定两个元素 | Tuple[int, str] |
tuple[int, ...] | 任意长度、元素都是 int | Tuple[int, ...] |
set[str] | 集合 | Set[str] |
str | None | str 或 None(3.10 起) | Optional[str] |
int | str | int 或 str(3.10 起) | Union[int, str] |
Any | 任意类型,关闭检查 | — |
object | 任意对象,但只能做所有对象都支持的操作 | — |
None(作返回类型) | 不返回值 | — |
内置容器直接写泛型(list[str])自 3.9 起可用(PEP 585);SDK 等需要兼容旧版本的库仍大量使用 List、Dict、Optional,两种写法含义相同。
运行时不检查
def double(x: int) -> int:
return x * 2
print(double(3))
print(double("ab")) # 类型不符,但照常运行:'abab'类型检查是静态的:编辑器(VS Code/PyCharm 内置的 pyright 等)或命令行工具(如 uvx mypy .、uvx pyright)在运行前分析代码。例如对上面的代码,类型检查器会报告 double("ab") 的参数类型不兼容。
需要运行时校验(如处理外部输入)时,要么手写 isinstance 检查,要么使用 Pydantic 等库——anthropic SDK 的响应模型正是借助 Pydantic 在运行时校验和转换数据。
注解的求值时机
- 3.13 及以前:注解在定义函数/类时立即求值。注解中引用尚未定义的类时,要写成字符串
"ClassName"(前向引用),或在文件顶部写from __future__ import annotations(所有注解变为字符串)。 - 3.14 起:注解延迟求值(PEP 649、PEP 749),前向引用不再需要引号;新增
annotationlib模块读取注解。
from __future__ import annotations # 兼容 3.12/3.13 的前向引用写法
class Node:
def __init__(self, value: int, next: Node | None = None): # 引用自身
self.value = value
self.next = next
n = Node(1, Node(2))
print(n.next.value, Node.__init__.__annotations__["next"])易错点
- 以为注解会阻止错误类型:不会,运行时完全不检查。
def f(x: list = []):注解不改变“可变默认值”的问题(3.2)。Optional[str]不等于“可选参数”:它只表示“可以是 None”;参数是否可省略取决于有没有默认值。- 把
list[str]用于isinstance:isinstance(x, list[str])抛TypeError;运行时只能检查isinstance(x, list)。
print(isinstance(["a"], list[str]))对照
| 概念 | Python | TypeScript | Java |
|---|---|---|---|
| 参数类型 | def f(x: int) | function f(x: number) | void f(int x) |
| 返回类型 | -> str | : string | 写在方法名前 |
| 可空 | str | None | string | null | @Nullable String / Optional<String> |
| 泛型容器 | list[str]、dict[str, int] | string[]、Record<string, number> | List<String>、Map<String, Integer> |
| 检查时机 | 静态工具(可选) | tsc 编译时 | javac 编译时(强制) |
| 运行时 | 不检查 | 类型被擦除 | 泛型被擦除,其余强制 |
可以把 Python 注解理解为“TypeScript 的类型,但不需要编译、也不强制”。
读代码
为 minimal_agent.py 的函数补上注解后:
def get_weather(city: str) -> str:
"""工具的真实实现:这里用固定结果代替真实天气接口。"""
return f"{city}:晴,22°C"
TOOL_FUNCTIONS: dict[str, Callable[..., str]] = {"get_weather": get_weather}
messages: list[dict[str, object]] = [{"role": "user", "content": "上海今天天气怎么样?"}]SDK 中真实的签名(anthropic 1.11.0,Messages.create 节选):
def create(
self,
*,
max_tokens: int,
messages: Iterable[MessageParam],
model: ModelParam,
stop_sequences: SequenceNotStr[str] | Omit = omit,
system: Union[str, Iterable[TextBlockParam]] | Omit = omit,
tools: Iterable[ToolUnionParam] | Omit = omit,
timeout: float | httpx2.Timeout | None | NotGiven = not_given,
) -> Message: ...读法:max_tokens、messages、model 没有默认值 → 必填;其余带 = omit(一个“未传”哨兵对象)→ 可选;返回 Message 对象。Iterable[...] 表示任何可迭代对象都行(list 当然可以)。
练习
练习 1:补全注解
为下面三个函数补上参数与返回值注解,使检查通过(检查读取 __annotations__):count_words(text) 返回 int;first_or_none(items) 接收 list[str],返回 str | None;merge(a, b) 接收两个 dict[str, int],返回 dict[str, int]。
def count_words(text):
return len(text.split())
def first_or_none(items):
return items[0] if items else None
def merge(a, b):
return a | b
练习 2:运行时校验
注解不做运行时检查。实现 checked_add(a: int, b: int) -> int:若 a 或 b 不是 int(注意 bool 也是 int 的子类,应拒绝),抛出 TypeError;否则返回和。
def checked_add(a: int, b: int) -> int:
return a + b
小结
- 注解语法:
参数: 类型、-> 返回类型、变量: 类型。 list[str]、dict[str, int]、X | None是现代写法,List/Dict/Optional是等价旧写法。- 运行时不检查;静态检查交给编辑器与 mypy/pyright,运行时校验靠
isinstance或 Pydantic。 - 3.14 起注解延迟求值;3.12/3.13 用字符串或
from __future__ import annotations处理前向引用。