子类重写抽象类方法触发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
相关产品推荐
相关产品推荐

