Python 3.12 让静态类型标注更接近开发者真正想表达的代码结构:泛型类型变量可以用更紧凑的语法声明,@override 能明确子类方法的重写意图,而 TypedDict 则适合描述字典形式的结构化数据。它们不会改变 Python 的运行时动态特性,却能让类型检查器更早暴露接口变更、字段拼写和继承关系中的问题。
用新语法声明类型变量
过去,泛型函数通常需要先导入 TypeVar,再单独创建类型变量:
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
Python 3.12 支持更直接的类型参数语法,可以把类型变量放在函数定义后面:
def first[T](items: list[T]) -> T:
return items[0]
def pair[T, U](left: T, right: U) -> tuple[T, U]:
return left, right
print(first([10, 20, 30]))
print(pair("job", 3))
将代码保存为 typing_demo.py,使用 Python 3.12 或更高版本运行:
python3.12 typing_demo.py
这类语法主要减少声明噪声。类型检查器仍然会推断 first([10, 20]) 返回 int,而 pair("job", 3) 返回 tuple[str, int]。如果项目仍需支持 Python 3.11 或更早版本,就应继续使用 TypeVar 形式,或者通过版本策略隔离新语法。
类型参数也可以带边界。例如,下面的函数要求参数必须是 str 的子类型:
def normalize[S: str](value: S) -> S:
return value.strip()
边界和约束应当服务于真实的 API 契约,而不是为了让标注看起来复杂。
用 @override 保护继承契约
子类方法拼写错误时,Python 通常只会把它当成一个新方法,运行到特定路径才暴露问题。@override 用来声明“这个方法应该重写父类中的同名方法”,类型检查器可以据此检查父类是否真的存在对应方法。
在 Python 3.12 中,override 位于 typing:
from typing import override
class Repository:
def save(self, value: str) -> None:
print(f"saving {value}")
class MemoryRepository(Repository):
@override
def save(self, value: str) -> None:
print(f"stored {value}")
如果把 save 错写成 svae,支持该检查的工具会报告重写失败。建议在 CI 中运行类型检查器,而不要只依赖代码审查:
python3.12 -m pip install mypy
mypy --strict typing_demo.py
@override 本身不负责改变运行时调度,也不会自动检查参数逻辑。它的价值在于把继承关系中的意图写进代码,并让工具在父类 API 变化时及时提醒你。
用 TypedDict 描述字典数据
很多 Python 服务使用字典承载 JSON 数据。直接写 dict[str, object] 虽然宽泛,却丢失了必需字段、可选字段和字段类型。TypedDict 能保留字典在运行时的原样行为,同时为静态分析提供结构信息:
from typing import NotRequired, TypedDict
class UserPayload(TypedDict):
id: int
name: str
email: NotRequired[str]
def display_user(user: UserPayload) -> str:
contact = user.get("email", "no email")
return f"#{user['id']} {user['name']} ({contact})"
print(display_user({"id": 7, "name": "Lin"}))
TypedDict 不会在运行时验证输入,因此来自 HTTP 请求、消息队列或数据库的数据仍然需要验证。可以把它看成“经过验证之后的数据形状”,而不是验证库的替代品。
当一个字典只有少数可选字段时,NotRequired 比把所有字段都标成可选更精确。对于整个类型都可选的场景,也可以使用 total=False,但要清楚这会改变所有字段的必需性。
落地时检查这几件事
升级到 Python 3.12 后,可以按以下顺序引入这些能力:
- 先在新代码或边界模块中使用类型参数语法,确认运行环境和工具链都支持 Python 3.12。
- 在有明确父类契约的代码中添加
@override,并让 mypy、Pyright 等工具进入 CI。 - 为 API 请求、事件消息和配置字典定义
TypedDict,同时保留运行时数据验证。 - 不要把静态类型当成运行时安全检查;类型标注和输入校验解决的是两个不同问题。
这三个特性组合起来,覆盖了泛型 API、继承 API 和结构化字典数据三个常见缺口。它们的共同点不是让 Python 变成静态语言,而是用更少的样板代码把开发者的意图交给工具检查。