Python 类型注解完全指南
Python 类型注解系统教程:基础类型、函数注解、Optional 与 Union、泛型、渐进式类型化策略,配合 Mypy 把动态语言写出静态语言的可靠。
直接回答:Python 类型注解(type hints)是写在代码里的类型契约:不会由解释器自动强制校验(Python 仍是动态语言),但让 IDE 补全飞起、重构有底气,配合 Mypy/Pyright 在运行前抓住类型错误。 注解可以渐进添加,一个函数一个函数地来。
基础类型
name: str = "张三"
age: int = 28
price: float = 9.9
active: bool = True
tags: list[str] = ["python", "tutorial"] # 3.9+ 内置泛型
scores: dict[str, int] = {"math": 95}
point: tuple[float, float] = (1.0, 2.0)
unique: set[int] = {1, 2, 3}
3.9 之后直接用 list[int],不用再 from typing import List。
函数注解
def greet(name: str, punct: str = "!") -> str:
return f"你好, {name}{punct}"
参数类型、返回值类型各就各位。函数始终不返回有意义的值时标 -> None,可以显式 return None,也可以自然结束。若有时返回业务值、有时返回 None,应使用相应联合类型;声明了非 None 返回类型的路径则需遵守契约。
Optional 与 Union
def find_user(uid: int) -> str | None: # 可能找不到
...
def parse(data: str | bytes) -> str: # 多种类型都行
...
str | None(3.10+)等价于老式 Optional[str]。标注"可能为 None"的价值立竿见影——启用相应检查后,检查器能提示未经缩窄就使用可能为 None 的值;它不能消除所有运行时错误。
泛型与自定义类
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
first([1, 2]).bit_count() # 检查器知道这是 int
first(["a"]).upper() # 这里知道是 str
class Stack[T]: # 3.12+ 泛型类语法
def push(self, item: T) -> None: ...
def pop(self) -> T: ...
泛型让容器类与工具函数"保类型"——放进去什么,拿出来还是什么。
常用高级类型速查
from typing import Callable, Iterable, Literal, TypedDict, Protocol
handler: Callable[[int], str] # 函数类型
items: Iterable[int] # 可迭代即可(最宽接口)
mode: Literal["fast", "safe"] # 字面量枚举
class User(TypedDict): # 结构化字典
name: str
age: int
class Drawable(Protocol): # 鸭子类型契约
def draw(self) -> None: ...
渐进式类型化:存量项目的正确姿势
- 优先为新代码的函数边界添加有用的注解;
- 公共 API 与核心模块优先补注解(收益密度最高);
- 检查器配置从宽松起步,按模块收紧;
- CI 加"覆盖率只升不降"门禁;
- 没类型存根的第三方库,先
Any或# type: ignore放行,别卡死推进。
渐进是特性不是妥协——Python 类型系统的设计哲学就是"你想多严就多严"。
常见问题(FAQ)
Q:注解会影响运行时性能吗?
A:解释器不会自动按注解校验每次调用,但保存和求值注解仍可能有成本。Python 3.14 默认延迟求值,并不等于零开销;运行时读取注解的框架可能执行求值或额外校验。
Q:所有代码都要注解吗?
A:函数签名(公共接口)必须;函数内部的局部变量靠推断即可,标注关键变量就够。过度注解和零注解一样不可取。
Q:运行时校验用什么?
A:类型注解本身不强制校验数据;运行时校验(用户输入、外部数据)用 Pydantic——两者搭档,各管一岸。
官方参考
本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。