如何对Python typing.Generic类型本身进行类型注解?
如何在Python类型标注中表达泛型类本身(泛型构造器)
你提到的这个问题其实是Python类型系统里一个经典的痛点:如何标注那些**接收泛型类本身(比如List)、绑定具体类型后返回该泛型实例类型(比如List[int])**的函数,也就是你说的functor(函子)类型。下面结合你的尝试一步步拆解解决方案:
先分析你之前尝试的问题
- 朴素方法用
Generic作为类型:Generic只是泛型类的基类,不是泛型构造器的类型,所以mypy会报错,它不代表“可以被[]索引的泛型类”。 GenericMeta:这是旧版typing库的内部实现细节,属于未公开的API,不仅没被PEP收录,在Python 3.7+的标准库中已经被移除,完全不推荐依赖。Mapping[Type, Type]:这个思路是错的,List不是Mapping,它只是支持__getitem__语法的鸭子类型,静态类型检查不会把它当成Mapping。- 宽松的
Type写法:虽然能拦截apply(10, int)这种明显错误,但无法阻止apply(int, int)——因为int也是Type,但它不支持[]索引,会在运行时炸锅。
正确解决方案:用PEP 544的Protocol定义泛型构造器协议
PEP 544引入的Protocol正好解决了这个鸭子类型的静态标注问题:我们可以定义一个协议,描述“支持用[]接收类型参数、返回绑定后泛型类型”的行为,让mypy识别List、Dict这类泛型类符合这个协议。
具体代码实现
from typing import TypeVar, Type, Generic, Protocol from typing_extensions import Self # Python 3.11+可以用标准库的typing.Self # 定义两个TypeVar:T代表泛型的参数类型,G代表绑定参数后的泛型类型(比如List[int]) T = TypeVar('T') G = TypeVar('G', covariant=True) class GenericConstructor(Protocol, Generic[T, G]): def __getitem__(self, item: Type[T]) -> Type[G]: ... def apply(generic: GenericConstructor[T, G], param: Type[T]) -> Type[G]: return generic[param] # 测试正确用法:mypy会识别出ListOfInts是List[int] ListOfInts = apply(list, int) assert ListOfInts == list[int] # 测试错误用法:mypy会报错,因为int不支持__getitem__接收类型参数 apply(int, int) # error: Argument 1 to "apply" has incompatible type "Type[int]"; expected "GenericConstructor[int, Any]"
关键说明
- Protocol的作用:
GenericConstructor协议定义了__getitem__的签名,只要一个类型实现了这个方法(比如list、dict这些泛型类),mypy就会认为它符合该协议,不需要显式继承。 - TypeVar的协变性:
G标记为covariant=True是因为泛型绑定后的类型(比如List[int])是泛型构造器(List)的协变产物,这样类型检查会更灵活。 - 兼容性:Python 3.9+可以直接用标准库的
typing.Protocol;3.8及以下需要安装typing-extensions库来导入Protocol和Self。
关于你提到的Functor
你说得完全没错!这种泛型构造器在范畴论里就是函子——它接收一个类型(范畴里的对象),返回另一个类型,同时保持类型之间的映射关系。Haskell里用* -> *这种kind来描述它,而Python通过Protocol实现了近似的静态类型约束。
内容的提问来源于stack exchange,提问作者R2RT
相关产品推荐
相关产品推荐

