泛型类中构造方法重载引发mypy报错的问题排查
@typing.overload def __init__(self, val_type: typing.Literal[EValType.FLOAT], val: float) -> None: ... def __init__(self, val_type: EValType, val: T) -> None: self.val_type: EValType = val_type self.val: T = val
if name == "main":
a = Foo(EValType.STRING, "hello")
b = Foo(EValType.FLOAT, 23)
c = Foo(EValType.STRING, 3.14) # mypy 应检测此处错误
运行mypy后,除了正确检测到最后一行的错误调用外,还报了两个关于重载实现的错误:
test.py:19: error: Overloaded function implementation does not accept all possible arguments of signature 1 [misc]
test.py:19: error: Overloaded function implementation does not accept all possible arguments of signature 2 [misc]
test.py:27: error: No overload variant of "Foo" matches argument types "EValType", "float" [call-overload]
test.py:27: note: Possible overload variants:
test.py:27: note: def [T in (str, float)] init(self, val_type: Literal[EValType.STRING], val: str) -> Foo[T]
test.py:27: note: def [T in (str, float)] init(self, val_type: Literal[EValType.FLOAT], val: float) -> Foo[T]
移除重载后mypy不再报错,但会丢失参数间的类型依赖,想知道这两个重载实现错误的原因是什么? --- # 错误原因 这两个错误的核心是**重载签名与实现签名的类型兼容性不匹配**,mypy要求重载的实现必须能完全兼容所有重载签名的输入类型: - 对于第一个重载签名(`val_type: Literal[EValType.STRING], val: str`):重载明确要求`val`是`str`类型,但实现中`val`的类型是泛型`T`,而`T`的约束是`str | float`。mypy会认为,当调用这个重载时,实现理论上可以接受`float`类型的`val`,这与重载签名的要求矛盾,因此判定实现不兼容该重载。 - 第二个重载签名的问题同理:重载要求`val`是`float`,但实现的`val`是`T`(可接受`str`),mypy认为实现无法保证只接受`float`类型,所以报错。 简单来说,你的实现通过泛型`T`同时覆盖了两种类型,但每个重载签名只允许一种特定类型,mypy无法确认实现会严格遵守每个重载的类型限制,因此抛出兼容性错误。 --- # 修复方案 要保留参数类型依赖并消除报错,需要让重载签名明确绑定对应的泛型实例,同时让实现的参数类型覆盖所有重载的输入范围: ```python import enum import typing class EValType(enum.Enum): STRING = 1 FLOAT = 4 T = typing.TypeVar("T", str, float) class Foo(typing.Generic[T]): # 明确指定每个重载对应的泛型实例类型 @typing.overload def __init__(self: Foo[str], val_type: typing.Literal[EValType.STRING], val: str) -> None: ... @typing.overload def __init__(self: Foo[float], val_type: typing.Literal[EValType.FLOAT], val: float) -> None: ... # 实现的参数类型覆盖所有重载的可能输入 def __init__(self, val_type: EValType, val: str | float) -> None: self.val_type = val_type self.val = val # type: ignore[assignment]
这样调整后,mypy能正确识别每个重载对应的泛型类型,同时实现的参数类型兼容所有重载的输入,报错会消失,且仍能检测到参数不匹配的错误调用。
内容的提问来源于stack exchange,提问作者user1109069

