泛型擦除发生在Python导入模块或执行字节码之前,由解释器在解析类型注解(__annotations__)时完成;源码中如list[str]在运行时变为list,下标信息丢失。

Python 的泛型在运行时会被完全擦除,只在类型检查阶段起作用。这是因为 Python 采用的是“类型提示”(type hints)机制,而非真正的静态类型系统。泛型参数(如 List[str]、Dict[int, bool] 或自定义的 Stack[T])在代码执行前就被丢弃,解释器看到的只是原始类型(如 list、dict、Stack)。
泛型擦除发生在哪个环节?
擦除发生在 Python 导入模块或执行字节码之前,由解释器在解析类型注解(__annotations__)时完成。具体来说:
- 源码中的
def foo(x: list[str]) -> dict[str, int]: ...在运行时,x的注解变成list,返回注解变成dict; -
typing.List[str](旧写法)和list[str](PEP 585 写法)都会被归一化为list,下标信息丢失; - 通过
get_type_hints()获取的也是擦除后的类型,除非显式传入include_extras=True并配合typing.get_args()等工具尝试还原(但无法恢复完整泛型结构)。
为什么 Python 要擦除泛型?
设计上为了兼容动态性与运行时灵活性:
- 不改变对象的实际行为:一个
list不会因为标注为list[str]就拒绝添加整数; - 避免运行时开销:无需在每次访问元素时检查类型,也不需为每种泛型组合生成新类;
- 保持与已有代码无缝衔接:所有类型提示都是可选的、非强制的,擦除后不影响任何逻辑执行。
擦除带来的实际影响
开发者需注意以下常见情况:
立即学习“Python免费学习笔记(深入)”;
-
无法在运行时做泛型参数判断:比如不能写
if T is str:,因为T是TypeVar对象,且其绑定信息不保留; -
序列化/反射受限:用
dataclasses或pydantic时,字段类型若含泛型(如items: list[UUID]),框架需靠额外机制(如字符串解析、AST 预处理)推断参数,而非直接读取运行时类型; -
自定义容器需手动处理:如果实现类似
Box[T]的类,要支持类型感知(如Box[int].__args__),必须依赖typing.get_args()解析原始注解字符串或 AST,不能靠实例属性直接获取。
有没有绕过擦除的方法?
严格来说没有“绕过”,但有折中方案:
- 使用
typing.get_origin()和typing.get_args()从注解对象中提取泛型结构(仅限静态可用的注解,且要求未被eval过度简化); - 在类定义中显式保存参数,例如:
class Stack(Generic[T]): def __init__(self, item_type: type[T]): self.item_type = item_type; - 借助第三方库如
typing-inspect或typing_extensions增强对泛型元数据的访问能力,但仍受限于擦除前提。










