继承类重载不同参数构造函数引发的Mypy类型错误解决方法
解决Mypy严格模式下的类构造函数类型错误
当以下代码在Mypy严格模式下运行时,会触发类型错误:
import abc class ABCParent(metaclass=abc.ABCMeta): def __init__(self, a: str, sibling_type: type[ABCParent]) -> None: self.a = a self._sibling_type = sibling_type def new_sibling(self, a: str) -> ABCParent: return self._sibling_type(a) class ChildA(ABCParent): def __init__(self, a: str) -> None: super().__init__(a, ChildB) class ChildB(ABCParent): def __init__(self, a: str) -> None: super().__init__(a, ChildA)
错误信息如下:
src/demo_problem.py: note: In member "new_sibling" of class "ABCParent": src/demo_problem.py:10:16:10:36: error: Missing positional argument "sibling_type" in call to "ABCParent" [call-arg] return self._sibling_type(a)
错误逻辑合理:ABCParent的构造函数要求传入sibling_type参数,但实际存储在self._sibling_type的是重写了构造函数、仅需一个参数的子类。
此前用typing.Union子类的方式存在诸多问题:
- 需定义无实际业务用途的类型别名
TConcreteChild - 新增子类时必须手动更新该别名的定义
- 不得不使用字符串形式的前向引用,无法使用
ChildA | ChildB这类新语法
更简洁的实现方案
方案一:用Protocol约束构造函数签名
通过typing.Protocol定义协议,明确规定具体子类的构造函数签名,让Mypy正确识别子类的构造参数要求:
import abc from typing import Protocol, Type, TypeVar # 定义协议,约束子类构造函数仅接收一个字符串参数 class ConcreteChildProtocol(Protocol): def __init__(self, a: str) -> None: ... # 泛型变量:绑定ABCParent及其子类 T = TypeVar('T', bound='ABCParent') # 泛型变量:绑定实现了ConcreteChildProtocol的类 SiblingT = TypeVar('SiblingT', bound=ConcreteChildProtocol) class ABCParent(metaclass=abc.ABCMeta): def __init__(self, a: str, sibling_type: Type[SiblingT]) -> None: self.a = a self._sibling_type = sibling_type def new_sibling(self, a: str) -> SiblingT: return self._sibling_type(a) class ChildA(ABCParent['ChildA', 'ChildB']): def __init__(self, a: str) -> None: super().__init__(a, ChildB) class ChildB(ABCParent['ChildB', 'ChildA']): def __init__(self, a: str) -> None: super().__init__(a, ChildA)
该方案优势:
- 无需维护无意义的类型别名,新增子类仅需在继承时指定泛型参数
- 支持精确的类型推导,
new_sibling的返回类型会被Mypy正确识别为对应子类 - 字符串形式的泛型参数是Python处理循环依赖的标准写法,避免了前向引用的麻烦
方案二:直接用Callable简化标注
如果不需要精确的子类返回类型,可直接将sibling_type标注为Callable[[str], ABCParent],代码更简洁:
import abc from typing import Callable class ABCParent(metaclass=abc.ABCMeta): def __init__(self, a: str, sibling_type: Callable[[str], ABCParent]) -> None: self.a = a self._sibling_type = sibling_type def new_sibling(self, a: str) -> ABCParent: return self._sibling_type(a) class ChildA(ABCParent): def __init__(self, a: str) -> None: super().__init__(a, ChildB) class ChildB(ABCParent): def __init__(self, a: str) -> None: super().__init__(a, ChildA)
这种写法同样能通过Mypy严格模式检查,适合对返回类型精度要求不高的场景。
内容的提问来源于stack exchange,提问作者mhooreman
相关产品推荐
相关产品推荐

