You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 00:23:19