You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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))

关键说明

  1. 给TypeVar添加bound=Shape,明确类型范围,避免mypy出现Never类型错误,同时让类型推断更精准。
  2. 方案一的Sequence是通用解决方案,兼容任意长度的输入;方案二更适合固定长度的输入场景,类型推断精度更高。

内容的提问来源于stack exchange,提问作者Ax_6

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:57:31