Mypy协变问题:基于输入子类类型列表的返回类型推断
Pydantic模型生成函数的类型注解优化(解决mypy报错)
我正在为一段生成Pydantic模型实例的代码添加类型注解,代码运行正常,但mypy报错。目标是让函数能根据输入参数推断返回列表的类型,当前VS Code能正确识别类型,但mypy不通过。
复现代码
from pydantic import BaseModel from typing import TypeVar class Shape(BaseModel): name: str class Circle(Shape): diameter: float class Square(Shape): side: float class Triangle(Shape): base: float height: float T = TypeVar("T") def generate_shapes(shapes: list[type[T]]) -> list[T]: generated_shapes = [] for shape in shapes: if shape == Circle: generated_shapes.append(Circle(name=shape.__name__, diameter=10)) elif shape == Square: generated_shapes.append(Square(name=shape.__name__, side=10)) elif shape == Triangle: generated_shapes.append(Triangle(name=shape.__name__, base=10, height=10)) return generated_shapes if __name__ == "__main__": result = generate_shapes([Circle, Triangle])
mypy报错信息
Argument 1 to "generate_shapes" has incompatible type "list[ModelMetaclass]"; expected "list[type[Never]]"
解决思路
问题根源是可变容器list不支持协变,mypy对可变类型的类型检查更严格。要同时满足VS Code的类型推断和mypy检查,有两种可行方案:
方案一:用只读序列Sequence替代list作为参数类型
Sequence是只读的抽象类型,支持协变,既能让mypy通过检查,也不影响VS Code的类型推断。同时给TypeVar添加类型上限,明确类型范围。
修改后的代码:
from pydantic import BaseModel from typing import TypeVar, Sequence class Shape(BaseModel): name: str class Circle(Shape): diameter: float class Square(Shape): side: float class Triangle(Shape): base: float height: float # 明确T是Shape的子类,缩小类型推断范围 T = TypeVar("T", bound=Shape) def generate_shapes(shapes: Sequence[type[T]]) -> list[T]: generated_shapes = [] for shape in shapes: if shape == Circle: generated_shapes.append(Circle(name=shape.__name__, diameter=10)) elif shape == Square: generated_shapes.append(Square(name=shape.__name__, side=10)) elif shape == Triangle: generated_shapes.append(Triangle(name=shape.__name__, base=10, height=10)) return generated_shapes if __name__ == "__main__": result = generate_shapes([Circle, Triangle])
方案二:使用Tuple作为输入类型(固定长度场景)
如果调用时传入的是固定长度的元组(比如(Circle, Triangle)),可以直接用tuple注解输入类型,让mypy做更精确的类型推断:
from pydantic import BaseModel from typing import TypeVar, Tuple class Shape(BaseModel): name: str class Circle(Shape): diameter: float class Square(Shape): side: float class Triangle(Shape): base: float height: float T1 = TypeVar("T1", bound=Shape) T2 = TypeVar("T2", bound=Shape) def generate_shapes(shapes: Tuple[type[T1], type[T2]]) -> list[T1 | T2]: generated_shapes = [] for shape in shapes: if shape == Circle: generated_shapes.append(Circle(name=shape.__name__, diameter=10)) elif shape == Square: generated_shapes.append(Square(name=shape.__name__, side=10)) elif shape == Triangle: generated_shapes.append(Triangle(name=shape.__name__, base=10, height=10)) return generated_shapes if __name__ == "__main__": result = generate_shapes((Circle, Triangle))
关键说明
- 给
TypeVar添加bound=Shape,明确类型范围,避免mypy出现Never类型错误,同时让类型推断更精准。 - 方案一的
Sequence是通用解决方案,兼容任意长度的输入;方案二更适合固定长度的输入场景,类型推断精度更高。
内容的提问来源于stack exchange,提问作者Ax_6
相关产品推荐
相关产品推荐

