Mypy 在 __init__ 覆盖中接受不兼容的类型

Mypy accepts an incompatible type in __init__ override

我有以下 Foo 基础 class,以及从它继承的 Bar

class Foo:
    def __init__(self, x: int) -> None:
        self._x = x

    def do(self, x: int) -> None:
        pass


class Bar(Foo):
    pass

如果我重写 Bar 中的 Foo.do,并更改 x 参数的类型以获得不兼容的内容(即不比 int 更通用),然后 Mypy returns 一个错误——这当然是我所期望的。

class Bar(Foo):
    def do(self, x: str) -> None:
        pass

错误:

test.py:10: error: Argument 1 of "do" is incompatible with supertype "Foo"; supertype defines the argument type as "int"
test.py:10: note: This violates the Liskov substitution principle
test.py:10: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Found 1 error in 1 file (checked 1 source file)

但是,如果我用不兼容的参数类型覆盖 __init__,Mypy 会接受它:

class Bar(Foo):
    def __init__(self, x: str) -> None:
        self._x = 12

Mypy 输出:

Success: no issues found in 1 source file

在我看来,用不兼容的类型覆盖 __init__ 也违反了 LSP,因为如果我们将 Foo 替换为 [=17],像 foo = Foo(12) 这样的代码不会进行类型检查=].

为什么 Mypy 接受我用不兼容的类型覆盖 __init____init__ 是否与其他方法区别对待? 另外,Mypy 这样做对吗?我是否正确认为最后一个 Bar class 违反了 LSP?

注意我真的不太确定。我让投票评判。

tl;dr LSP 不适用于 __init__,因为它不能用作实例上的方法。

LSP states

Subtype Requirement: Let Q(t) be a property provable about objects t of type T. Then Q(s) should be true for objects s of type S where S is a subtype of T.

对于do,我们有

Q(x) = signature of x.do
t = Foo(...)
T = Foo
s = Bar(...)
S = Bar

并且由于 doFoo 的所有实例具有相同的签名(即它是 T 的对象 t 的可证明 属性),这对于对象 t 也必须为真输入 Bar。但是,对于 __init__,它实际上是 class 上的一个方法,我们有

t = Foo
T = type[Foo]
s = Bar
S = type[Bar]

在这种情况下,关于 __init__ 类型 type[Foo] 的对象 t(即 classes)没有可证明的 属性,因为这包括 FooBar,因此 LSP 对 Bar.

的签名只字未提

一般认为里氏替换原则不适用于构造方法。如果我们将构造函数方法视为对象接口的一部分,那么在许多情况下,继承系统将变得极其难以管理,并导致一大堆其他的复杂情况。请参阅 this question 我前阵子在软件工程上发表的文章。

然而,情况有点复杂,因为 __init__ 并不是 真正的构造函数方法(应该是 __new__)—它是一个初始化方法,可以在同一个实例上多次调用。初始化方法几乎总是与构造方法具有相同的签名只是“碰巧”。

由于 __init__ 可以在同一个实例上多次调用,就像被视为对象接口一部分的“普通”方法一样,目前 active discussion核心开发人员关于 __init__ 方法 是否应该 在某些方面被视为对象接口的一部分。

总结: ¯\_(ツ)_/¯

Python 是一种非常 的动态语言,这意味着对其类型系统的推理通常会有些奇怪。