Python 3.12 改用 class MyClass[T]: 替代 typing.Generic[T],TypeVar 仍需导入并用于类头泛型参数,协变/逆变需显式声明,Self[T] 支持泛型守恒,运行时依赖新解析机制。

Python 3.12 的 Generic 语法变了,别再用 typing.Generic[T]
Python 3.12 正式弃用了 typing.Generic 作为基类的旧写法,改用原生语法 class MyClass[T]:。这不是可选项——如果你用旧写法,type checkers(如 mypy 1.10+)会报错,运行时虽不崩溃,但语义已不被推荐。
关键变化:泛型参数现在直接跟在类名后、冒号前,像函数注解一样;不再需要继承 Generic,也不再需要从 typing 导入 Generic 或 TypeVar(除非你要约束类型)。
-
TypeVar仍需导入(如需类型约束),但声明方式不变:T = TypeVar('T', int, str) - 多个参数写成
class Box[T, U]:,等价于旧版class Box(Generic[T, U]): - 协变/逆变需显式用
covariant=True等参数传给TypeVar,不能靠继承Generic隐式控制
带约束的泛型类怎么写?TypeVar 还得用,但位置变了
想限制泛型只能是数字或字符串?TypeVar 没被移除,只是不再和 Generic 绑定。你依然要定义它,但把它放在类头泛型参数列表里即可。
from typing import TypeVar
<p>T = TypeVar('T', int, float, bound=str) # 注意:bound 和 constraints 不能共存</p><p>class Stack[T]: # ← 这里 T 是泛型参数名,不是变量
def <strong>init</strong>(self) -> None:
self._items: list[T] = []</p><pre class="brush:php;toolbar:false;">def push(self, item: T) -> None:
self._items.append(item)
注意:类头的 [T] 中的 T 必须是已定义的 TypeVar 对象(或 ParamSpec 等),不能是字符串字面量。如果写成 class Stack['T']:,会报 SyntaxError。
- 错误示范:
class Stack[T: int]:—— Python 3.12 不支持这种 inline bound 语法 - 正确做法:用
bound=...或constraints=(...)定义TypeVar,再在类头引用 -
type checkers依赖这个TypeVar对象做推导,所以漏掉from typing import TypeVar会导致类型检查失效
Self 和泛型类一起用时,为什么 -> Self 不够?
在泛型类中返回自身实例(比如链式调用),仅写 def add(self, x: T) -> Self: 是不够的——Self 表示“当前具体化类型”,但没携带泛型参数信息。如果子类继承并指定了具体类型,Self 会自动适配;但如果父类本身是泛型,且你想让方法保持相同泛型结构,就得显式写成 -> Self[T](Python 3.12+ 支持)。
class Container[T]:
def __init__(self, value: T) -> None:
self.value = value
<pre class="brush:php;toolbar:false;">def map[U](self, f: Callable[[T], U]) -> Container[U]: # ← 返回新泛型,非 Self
return Container(f(self.value))
def clone(self) -> Self[T]: # ← 显式标注泛型参数,确保类型守恒
return Container(self.value)
这里 Self[T] 告诉类型检查器:返回的是和当前实例完全一致的泛型实例(比如 Container[int] → Container[int]),而不是擦除泛型的 Container 原始类。
- 不用
Self[T]而用-> Container[T]也行,但失去继承友好性(子类调用会返回父类类型) -
Self[T]是 Python 3.12 新增语法,3.11 及更早版本不识别,会报错 - 如果方法不改变泛型参数,又希望支持子类,
Self[T]是目前最准的表达
运行时获取泛型参数?别指望 __orig_bases__ 自动解析
Python 3.12 的新语法在运行时仍通过 __orig_bases__ 暴露泛型信息,但解析逻辑变了:以前靠 Generic 基类触发处理,现在靠类定义中的方括号语法触发。这意味着,如果你手动构造类或动态生成泛型类,不能只改类头语法,还得确保 __orig_bases__ 正确设置。
例如,这个写法在 3.12 中无效:
class Base: pass
MyList = type('MyList', (Base,), {'__orig_bases__': (Base[T],)}) # ❌ 不触发泛型解析
而必须用 types.GenericAlias 或标准定义方式:
from types import GenericAlias
<p>class MyList[T]: ...</p><h1>等价于:</h1><p>MyList = MyList.<strong>class</strong>('MyList', (), {'<strong>parameters</strong>': (T,)}) # 复杂且不推荐
</p>
- 绝大多数场景下,直接写
class X[T]:即可,运行时反射(如get_origin/get_args)能正常工作 - 元编程或框架开发中若需动态构造,优先考虑
GenericAlias+ 具体类型,而非伪造__orig_bases__ -
typing.get_args(MyList[int])在 3.12 中返回(,行为一致,但底层机制已切换,)
泛型类定义看着只是语法糖,但背后涉及类型检查器解析、运行时反射、继承链推导三套逻辑的同步更新。最容易忽略的是:你以为删掉 Generic 继承就完事了,其实 TypeVar 定义位置、Self[T] 的显式标注、以及动态构造时的兼容写法,全都在暗处卡着你的类型安全。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











