比较bound为int|float的TypeVar值时为何触发mypy报错?
mypy对bound为int|float联合类型的TypeVar报比较运算不支持错误的根本原因
问题场景
对类型标注为TypeVar('T', bound=int|float)的变量使用内置二元比较运算时,会触发意料之外的mypy类型报错。
最小复现代码
import typing T = typing.TypeVar('T', bound=typing.Union[int, float]) def less_than(x: T, y: T) -> bool: return x < y
触发的报错信息
test.py:6: error: Unsupported operand types for < ("int" and "T")
补充验证现象
- 将类型定义替换为
T = typing.Union[int, float]时,mypy不会抛出上述报错 - 将类型定义替换为
T = typing.TypeVar('T', int, float)(将int、float作为TypeVar的独立约束参数,而非bound的联合类型)时,mypy也不会抛出上述报错
报错根本原因
这个报错是mypy的类型推导规则、TypeVar的语义差异、比较运算符的校验逻辑三者共同作用的结果:
- 首先明确三种写法的语义差异:
T = TypeVar('T', bound=Union[int, float]):表示T是Union[int, float]的任意子类型。由于int、float在Python类型系统中允许被继承,mypy会默认存在用户自定义int/float子类的可能性,T的实际类型范围比“仅int或float本身”更大。T = Union[int, float]:表示参数类型只能是int或者float本身,不存在其他可能类型。T = TypeVar('T', int, float):表示T只能被绑定为int、float两个具体类型之一,不存在其他自定义子类的可能。
- 其次是mypy对
<比较运算符的校验逻辑:Python的比较运算依赖类的__lt__等魔术方法实现,int.__lt__声明的合法参数类型为int,float.__lt__声明的合法参数类型为float。校验x < y的合法性时,mypy会先取x的静态类型,检查该类型的__lt__方法是否接受y的静态类型作为参数。 - 回到报错场景:当T是带bound的泛型时,mypy推导x的类型时,无法确定x到底是int的子类型还是float的子类型:如果x是int类型,它的
__lt__只接受int参数,但y的类型T可能是float的子类型,无法证明一定符合参数要求;反过来如果x是float类型,也存在y是int子类型不匹配的可能,因此直接抛出操作数不支持的错误。 - 另外两种写法不报错的核心原因:
- 直接使用
Union[int, float]作为参数类型时,mypy会对联合类型做全分支校验:分别验证x是int时y为int/float的比较合法性、x是float时y为int/float的比较合法性,所有分支都合法,因此不报错。 - 使用带两个类型约束参数的TypeVar时,mypy明确知道T只能是int或float本身,不存在其他未知子类,同样会遍历两个类型的组合做校验,所有比较组合都合法,因此不报错。
- 直接使用
内容的提问来源于stack exchange,提问作者jfschaefer
相关产品推荐
相关产品推荐

