使用mypy时方法签名与超类型不兼容的问题及后续疑问
解答你的mypy类型兼容性问题
第一个问题:为什么最初的子类方法签名不兼容?
你最初遇到的"foo的签名与超类型Base不兼容"错误,核心原因是子类方法违反了里氏替换原则(LSP):
父类Base的foo方法定义了可变位置参数*args: str,这意味着调用这个方法时可以传入任意数量的字符串参数(包括0个、1个、多个)。但子类A的foo只接受单个位置参数x: str,如果有代码把A的实例当作Base类型来使用(比如写def bar(b: Base) -> None: b.foo("a", "b")),调用foo("a", "b")就会直接抛出参数数量不匹配的错误——这不符合父类方法的契约,所以mypy会报错。
简单来说:子类方法的参数列表必须兼容父类的参数列表,即子类方法必须能接受父类方法允许的所有参数组合。
第二个问题:为什么修改参数后返回类型的变化没问题?
你把A.foo改成接受*args: str后,mypy不再报错,即使父类返回类型是Union[NoReturn, str],而子类返回类型是str——这是因为返回类型在方法重写时支持协变,而且NoReturn在这里的语义是特殊的:
Union[NoReturn, str]表示父类的foo要么返回一个字符串,要么永远不会正常返回(比如一直抛出异常,就像父类实现里的raise NotImplementedError)。- 子类的
foo总是返回str,这是父类返回类型的一个子集:任何期望父类foo返回值的代码,要么能处理str,要么会因为父类方法抛出异常而终止。子类方法排除了“永远不返回”的情况,但这是安全的——调用方的逻辑依然能正常工作(毕竟如果子类不会抛出异常,调用方不需要处理程序终止的情况,反而更简单)。
mypy允许这种返回类型的收缩,因为它没有破坏父类方法的契约:子类方法的返回值完全符合父类方法声明的可能返回值范围(父类说可能返回str或不返回,子类说一定返回str,这是合法的)。
内容的提问来源于stack exchange,提问作者mmhl
相关产品推荐
相关产品推荐

