Python继承中备用构造函数违反LSP的mypy类型检查问题
解决子类重写备用构造函数的LSP类型检查问题
问题背景
你用@classmethod实现from_briefcase作为备用构造函数,用来从dataclass重建业务对象,但子类BiggerBusinessMan重写该方法时,mypy报错说违反了里氏替换原则(LSP)。你希望这个方法像__init__一样,作为构造逻辑的一部分允许子类适配对应的数据类类型,而非严格的接口方法,同时还要保留强类型、不用# type: ignore、支持无Briefcase实例创建对象的需求。
解决方案1:泛型绑定实现类型关联
通过泛型把业务类和对应的dataclass类型绑定,让mypy识别出子类的from_briefcase是针对特定dataclass的合法构造逻辑,而非违反LSP的接口变更。
修改后的代码
from dataclasses import dataclass from typing import Self, TypeVar, Generic # 定义只能是Briefcase或其子类的泛型变量 T = TypeVar('T', bound='Briefcase') @dataclass class Briefcase: item1: str item2: str @dataclass class BiggerBriefcase(Briefcase): item3: str # 让BusinessMan继承Generic[T],关联对应的dataclass类型 class BusinessMan(Generic[T]): def __init__(self): self.item1 = "" self.item2 = "" @classmethod def from_briefcase(cls, briefcase: T) -> Self: businessman = cls() businessman.item1 = briefcase.item1 businessman.item2 = briefcase.item2 return businessman # 指定BiggerBusinessMan对应的dataclass是BiggerBriefcase class BiggerBusinessMan(BusinessMan[BiggerBriefcase]): def __init__(self): super().__init__() self.item3 = "" @classmethod def from_briefcase(cls, briefcase: BiggerBriefcase) -> Self: businessman = super().from_briefcase(briefcase) businessman.item3 = briefcase.item3 return businessman # 使用示例 basic_briefcase = Briefcase("notes", "cards") bigger_briefcase = BiggerBriefcase("pens", "pencils", "letters") basic_businessman = BusinessMan[Briefcase].from_briefcase(basic_briefcase) bigger_businessman = BiggerBusinessMan.from_briefcase(bigger_briefcase)
方案解释
- 泛型
T绑定到Briefcase,确保传入的dataclass都是合法的子类,保证类型安全。 - 父类
BusinessMan继承Generic[T]后,from_briefcase的参数类型会随子类指定的T动态变化。 - 子类
BiggerBusinessMan明确关联BiggerBriefcase,此时重写from_briefcase使用更具体的参数类型,mypy会认为这是泛型的合法具体化,不再触发LSP错误。
解决方案2:用Protocol定义构造接口
如果不想引入泛型,也可以用Protocol定义父类from_briefcase接受的最小接口,让子类的dataclass满足这个接口,这样子类方法接受更具体的类型也符合LSP。
代码示例
from dataclasses import dataclass from typing import Self, Protocol # 定义父类from_briefcase需要的最小接口 class BriefcaseCore(Protocol): item1: str item2: str @dataclass class Briefcase(BriefcaseCore): item1: str item2: str @dataclass class BiggerBriefcase(BriefcaseCore): item1: str item2: str item3: str class BusinessMan: def __init__(self): self.item1 = "" self.item2 = "" @classmethod def from_briefcase(cls, briefcase: BriefcaseCore) -> Self: businessman = cls() businessman.item1 = briefcase.item1 businessman.item2 = briefcase.item2 return businessman class BiggerBusinessMan(BusinessMan): def __init__(self): super().__init__() self.item3 = "" @classmethod def from_briefcase(cls, briefcase: BiggerBriefcase) -> Self: businessman = super().from_briefcase(briefcase) businessman.item3 = briefcase.item3 return businessman
方案解释
BriefcaseCore协议定义了父类构造所需的核心属性,所有dataclass只要实现这些属性就符合要求。BiggerBriefcase满足BriefcaseCore协议,所以子类的from_briefcase接受它作为参数时,本质上还是符合父类方法的接口要求,mypy不会报错。
内容的提问来源于stack exchange,提问作者Quantasm
相关产品推荐
相关产品推荐

