Mypy报[override]错误‘方法签名与超类型不兼容’是否为误判?
我最近碰到一个挺费解的问题:明明子类的方法签名从**里氏替换原则(LSP)**来看完全合规,但mypy却抛出了Signature of "method" incompatible with supertype "Super"的[override]错误。先跟大家说下我的环境:
- Python 3.13.1
- mypy 1.14.0
我写了这样一个test.pyi类型提示文件:
from typing import overload class Super: def method(self, arg:Other|Super)->Super: pass class Sub(Super): @overload def method(self, arg:Other|Sub)->Sub: pass @overload def method(self, arg:Super)->Super: pass class Other: pass
运行mypy test.pyi后,得到了如下错误输出:
test.pyi:7: error: Signature of "method" incompatible with supertype "Super" [override] test.pyi:7: note: Superclass: test.pyi:7: note: def method(self, arg: Other | Super) -> Super test.pyi:7: note: Subclass: test.pyi:7: note: @overload test.pyi:7: note: def method(self, arg: Other | Sub) -> Sub test.pyi:7: note: def method(self, arg: Super) -> Super Found 1 error in 1 file (checked 1 source file)
我仔细对比了父类和子类方法的输入输出类型,完全没找到违反LSP的地方:
子类的重载方法其实完全覆盖了父类的输入范围(Other|Super = Other|Sub + Super),输出类型也满足LSP要求——要么和父类一致,要么是更窄的子类型。
我还做了一张输入输出对照表来验证:
| I\O | Super.method | Sub.method | 对比父类,子类返回类型 | 是否符合LSP* |
|---|---|---|---|---|
Other | Super | Sub | 更窄 | 是 |
Sub | Super | Sub | 更窄 | 是 |
Super | Super | Super | 一致 | 是 |
Other|Sub | Super | Sub | 更窄 | 是 |
Other|Super | Super | Super** | 一致 | 是 |
*LSP要求子类方法的返回类型必须是父类返回类型的子类型(更窄)或完全一致。
**当输入为Other|Super时,子类方法会根据输入分别返回Sub或Super,最终返回类型是Sub|Super,而Sub是Super的子类,所以整体等价于Super。
从表格能看出来,所有场景都符合LSP规范,那为什么mypy会报错呢?是我的代码哪里写错了,还是mypy的误判?如果是mypy的问题,我该去哪里反馈?
临时规避方法(非完美解决方案)
我找到了一个能暂时隐藏错误的办法,虽然不是根本解决方法,但可以先应付当前场景:
from typing import overload class Super: def method(self, arg:Other|Super)->Super: pass class Sub(Super): @overload def method(self, arg:Other|Sub)->Sub: pass @overload def method(self, arg:Super)->Super: pass # 开始隐藏错误 @overload def method( # type: ignore[overload-cannot-match] self, arg:Other|Super)->Super: pass # 结束隐藏错误 class Other: pass
就是在子类的重载最后,加一个和父类完全一致的签名。不过等未来mypy修复这个问题后,这个新增的重载会因为无法匹配触发[overload-cannot-match]错误,所以我提前加了# type: ignore[overload-cannot-match]来规避。(这不是单纯用ignore压制错误,而是提前预防未来可能出现的问题)
备注:内容来源于stack exchange,提问作者平田智剛

