Mypy为何允许重写__init__时使用不兼容的参数类型?
Mypy对__init__的特殊处理原因
- LSP约束的是实例被创建之后的使用行为,而非实例的创建过程。LSP的核心要求是「所有接收父类实例作为参数的上下文,都可以无感知替换为子类实例」,你构造实例时主动指定类名的场景(比如
Bar(12))不属于这个范畴:你本来就知道自己正在实例化哪个类,不需要和父类的构造参数对齐。 __init__不属于类的实例对外暴露的公共接口,只有实例化之后能被外部调用的方法/属性才属于LSP的约束范围。因此Mypy在子类定义阶段,不会对__init__、__new__这类构造相关的特殊方法做和普通实例方法一样的签名兼容校验,这是有意设计的规则,和普通方法的处理逻辑确实不同。
这个设计的合理性
- 这种规则是符合Python生态的通用实践的:比如标准库中绝大多数抽象基类的子类,构造参数都和父类不同,但只要实现了父类要求的实例接口,就可以正常替换使用。如果强制要求构造参数和父类对齐,反而会扼杀大量合理的继承场景。
- 如果你真的需要对构造过程做通用抽象,比如写一个接收
Type[Foo]类型参数、动态实例化的函数,Mypy依然会做对应的类型校验:
也就是说Mypy只会在你实际把类对象当成from typing import Type def create_foo(cls: Type[Foo], x: int) -> Foo: return cls(x) create_foo(Bar, 12) # Mypy此处会报错,提示Bar的__init__不接受int类型参数Type[Foo]来调用构造的时候才会做校验,不会在子类定义阶段就一刀切禁止不同的构造签名。
关于LSP的判定
你给出的示例中Bar类并没有违反LSP:只要Bar的实例完全实现了Foo对外暴露的所有实例接口,构造参数不同不会影响实例创建后的替换使用。只有当你修改__init__导致实例的属性/方法不符合父类的接口约定时,才会真正违反LSP,这种情况下Mypy也会对应抛出错误。
内容的提问来源于stack exchange,提问作者Right leg
相关产品推荐
相关产品推荐

