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

Rig*_*leg 4 python types liskov-substitution-principle mypy python-typing

我有以下Foo基类,并且Bar继承自它:

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

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


class Bar(Foo):
    pass
Run Code Online (Sandbox Code Playgroud)

如果我重写Foo.doin Bar,并将参数的类型更改x为不兼容的内容(即不比 更通用int),那么 Mypy 将返回错误 - 这当然是我所期望的。

class Bar(Foo):
    def do(self, x: str) -> None:
        pass
Run Code Online (Sandbox Code Playgroud)

错误:

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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

Mypy 输出:

Success: no issues found in 1 source file
Run Code Online (Sandbox Code Playgroud)

在我看来,__init__用不兼容的类型覆盖也会违反 LSP,因为如果我们替换为,类似的代码foo = Foo(12)不会进行类型检查。FooBar

为什么 Mypy 接受我__init__用不兼容的类型覆盖?__init__与其他方法的处理有何不同?另外,Mypy 这样做是否正确?我认为最后一Bar类违反了 LSP,对吗?

Ale*_*ood 5

通常认为里氏替换原则不适用于构造函数方法。如果我们将构造函数视为对象接口的一部分,那么在许多情况下,继承系统将变得极其难以管理,并导致一系列其他复杂情况。请参阅我不久前在软件工程中提出的这个问题。

\n

然而,情况有些复杂,因为它实际上__init__ 并不是一个构造函数方法(即__new__)\xe2\x80\x94,它是一个初始化方法,并且可以在同一个实例上多次调用它。只是“碰巧”初始化方法几乎总是与构造函数方法具有相同的签名。

\n

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

\n

总结一下: \xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf

\n

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

\n