重写__add__方法时Mypy提示与父类型不兼容的问题咨询
原始代码
class Person: number: int def __init__(self) -> None: self.number = 0 def __add__(self, other: 'Person') -> 'Person': self.number = self.number + other.number return self class Dan(Person): def __init__(self) -> None: super().__init__() def __add__(self, other: 'Dan') -> 'Dan': self.number = self.number + other.number + 1 return self
Mypy错误输出
test.py:15: error: Argument 1 of "add" is incompatible with supertype "Person"; supertype defines the argument type as "Person" [override]
test.py:15: note: This violates the Liskov substitution principle
test.py:15: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
问题说明
我认为子类型Dan比Person更具体,不应违反里氏替换原则。最初尝试创建绑定Person的TypeVar person_co = TypeVar("person_co", bound=Person),但因Person未定义而失败;又尝试将Dan的__add__方法参数和返回类型改为Person,Mypy不再报错,但我认为这并非正确做法,想寻求正确的解决方案。
正确解决方案
方法一:使用Python 3.11+的Self类型(推荐)
直接利用标准库的Self类型标注方法的参数和返回值,让类型系统自动推导子类的自引用类型:
from typing import Self class Person: number: int def __init__(self) -> None: self.number = 0 def __add__(self: Self, other: Self) -> Self: self.number += other.number return self class Dan(Person): def __add__(self: Self, other: Self) -> Self: self.number += other.number + 1 return self
方法二:使用泛型TypeVar(兼容旧Python版本)
如果使用Python 3.10及以下版本,可以通过定义自引用TypeVar来实现:
from typing import TypeVar # 用字符串形式绑定Person,避免未定义问题 T = TypeVar('T', bound='Person') class Person: number: int def __init__(self) -> None: self.number = 0 def __add__(self: T, other: T) -> T: self.number += other.number return self class Dan(Person): def __add__(self: 'Dan', other: 'Dan') -> 'Dan': self.number += other.number + 1 return self
原理说明
原代码违反里氏替换原则的核心原因:父类Person的__add__接受任意Person类型实例,但子类Dan的__add__仅接受Dan类型。当把Dan当作Person使用时,传入普通Person实例会触发类型错误,破坏了子类可替换父类的要求。
使用自引用类型后,__add__方法被约束为仅接受与自身同类型的实例:
- 调用
Person()的__add__时,必须传入Person实例 - 调用
Dan()的__add__时,必须传入Dan实例
同时,Dan作为Person的子类,在任何需要Person的场景中,其__add__方法也能遵循父类的类型约束,完全符合里氏替换原则。
内容的提问来源于stack exchange,提问作者Shane Heldsinger

