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

子类重写抽象类方法触发mypy报错,为何违反里氏替换原则?

子类方法参数类型缩小引发的mypy类型检查错误

问题描述

我在使用mypy时遇到了问题。我定义了一个抽象类Base,以及继承自它的子类MyClass,代码如下:

from __future__ import annotations

from abc import abstractmethod, ABC
from typing import Union


class Base(ABC):
    @abstractmethod
    def the_method(self, a_class: Union[Base, float, int]) -> None:
        ...

    @abstractmethod
    def other_method(self) -> None:
        ...


class MyClass(Base):
    def __init__(self, something: str = "Hello") -> None:
        self.something = something

    def the_method(self, a_class: Union[MyClass, float, int]) -> None:
        print(a_class)

    def other_method(self) -> None:
        print(self.something)

我了解里氏替换原则(Liskov substitution principle),且MyClass继承自Base,属于Base类型,但mypy仍抛出错误:

main.py:21: error: Argument 1 of "the_method" is incompatible with supertype "Base"; supertype defines the argument type as "Base | float | int"  [override]
main.py:21: note: This violates the Liskov substitution principle
main.py:21: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Found 1 error in 1 file (checked 1 source file)

请问我哪里出错了?

问题原因与解决

你这里的问题出在子类方法的参数类型被缩小了,违反了里氏替换原则的核心要求:子类必须能完全替代父类的所有使用场景。

具体来说,父类Base的the_method接受的参数范围是Union[Base, float, int]——任何Base的实例(包括其他子类实例)、浮点数、整数都能传入。但子类MyClass把参数改成Union[MyClass, float, int]后,这个方法就只能接受MyClass实例、浮点数、整数,无法兼容Base的其他子类实例。

按照里氏替换原则,当用MyClass实例替换Base实例时,所有原本能调用Base的the_method的场景都必须正常工作。比如假设有另一个Base的子类AnotherClass,如果有代码接收Base类型对象并调用the_method(AnotherClass()),当这个对象是MyClass实例时,就会出现参数类型不匹配的问题——这正是mypy报错的原因。

你有两种解决方式:

  • 保持子类方法的参数类型与父类完全一致,即使用Union[Base, float, int]
  • 如果确实需要限制参数为子类类型,可以通过泛型实现,修改后的代码如下:
from __future__ import annotations

from abc import abstractmethod, ABC
from typing import Union, TypeVar

T = TypeVar('T', bound='Base')

class Base(ABC):
    @abstractmethod
    def the_method(self, a_class: Union[T, float, int]) -> None:
        ...

    @abstractmethod
    def other_method(self) -> None:
        ...

class MyClass(Base['MyClass']):
    def __init__(self, something: str = "Hello") -> None:
        self.something = something

    def the_method(self, a_class: Union[MyClass, float, int]) -> None:
        print(a_class)

    def other_method(self) -> None:
        print(self.something)

这样修改后,mypy会认可这个类型定义,同时也能满足你限制参数为子类类型的需求。

内容的提问来源于stack exchange,提问作者niaei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 09:43:25