前沿与实践 · Frontier & Practice
lv.4 前沿
kp-040
类型系统前沿(PEP 695、Self、Protocol 进阶)
1. 一句话定义
2023 年起的类型系统更新把"能用"推向"好用":PEP 695 的新泛型语法(def f[T])、PEP 673 的 Self、TypeAlias 默认化——类型标注的书写成本显著下降。
2. 为什么重要
旧泛型写法(TypeVar + 显式声明)繁琐到劝退;新语法让"写类型"和"写代码"的成本打平,泛型从进阶玩具变成日常工具——这是类型化 Python 的普及拐点。
3. 前置知识
kp-026(类型注解基础)。
4. 核心概念
python
# ① 新泛型语法(PEP 695,3.12+):类型变量就地声明
def first[T](xs: list[T]) -> T:
return xs[0]
class Stack[T]:
def push(self, item: T) -> None: ...
def pop(self) -> T: ...
# ② 带约束/上界的类型变量
def clamp[T: (int, float)](v: T, lo: T, hi: T) -> T: ...
def scale[T: Number](x: T) -> T: ... # 上界(协议/类)
# ③ Self(PEP 673):返回"自己的类型",子类调用也正确
from typing import Self
class Builder:
def set_name(self, name: str) -> Self: # 旧写法会退化为 Builder
self.name = name
return self
class FastBuilder(Builder): ...
b = FastBuilder().set_name("x") # 推断为 FastBuilder ✓
# ④ 类型别名新写法(3.12+,惰性求值)
type Vector = list[float]
type Matrix = list[Vector]
# ⑤ 泛型默认值(PEP 696,3.13)
class Result[T = str]: ...5. 原理与机制
text
旧:T = TypeVar("T")
def first(xs: list[T]) -> T: ... (声明与使用分离)
新:def first[T](xs: list[T]) -> T: ... (声明即使用,作用域限定在函数内)
收益:
① 少两行样板,作用域自动收敛(不会误用别处的 T)
② 约束/上界直接写在 [T: ...] 括号里
③ 别名 type X = ... 支持前向引用,不再需要字符串引号运行时视角:PEP 695 引入了 TypeAliasType 对象,惰性求值——别名定义时不解析内部类型,检查期才解析。
6. 关键事实(模型/图示)
text
迁移指引:
旧代码 → 新写法(3.12+)
TypeVar(...) → def f[T](...)
TypeAlias 注解 → type X = ...
-> "ClassName" → -> Self(方法链场景)
旧约束 TypeVar 约束元组 → [T: (A, B)]
不变:运行时依然不强制;检查器才是裁判7. 直观类比
旧 TypeVar 像提前领工牌:先去前台登记(T = TypeVar("T"))才能进车间使用;PEP 695 像车间门口现做工牌——进哪个车间(函数/类)就做哪张,出车间自动作废,再也不会拿错别车间的牌子。
8. 实例与案例
python
# Builder 模式的正确类型化(Self 的杀手场景)
type JSON = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
def flatten(obj: JSON) -> list[str]: ...9. 常见误区
- 3.12 以下用新语法 ——
def f[T]是 3.12 语法;旧解释器用TypeVar。 - 用
-> "ClassName"模拟 Self —— 子类链上会退化为基类类型;链式返回一律Self。 - 以为
type X = ...是运行时赋值 —— 它创建惰性别名对象,非普通变量赋值。 - 类型变量的作用域混乱 —— 新语法的作用域即所在函数/类,不要再跨函数复用同名 T。
10. 自测题
Self与-> "Builder"在子类场景的差异?- PEP 695 的类型别名为什么天然支持前向引用?
def clamp[T: (int, float)]中(int, float)是约束还是上界?两者区别?
参考答案
Self随实际类流动(子类返回子类);字符串标注是死板的基类名。type别名是惰性求值的TypeAliasType,定义时不解析右侧,检查期才展开。- 约束(constrained):T 必须恰好是其中之一或其子类且保持具体类型;上界(bound):T 是该类型的任意子类型。约束保留精度,上界放宽。
11. 与其他知识点的关系
12. 延伸阅读
- PEP 695 / 673 / 696 原文
- typing 官方文档:https://docs.python.org/zh-cn/3/library/typing.html