1

根据 mypy 文档,如果一个类需要引用自身,它可以使用forward-reference

这似乎适用于普通类,但我无法让它与继承自 NamedTuple 的类一起使用。

"""
All this code runs without error on Python 3.6 

The question is why the 'B' class' __add__ method
raises an error through mypy.
"""


from typing import *

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

    def __add__(self, other: 'A') -> 'A':
        return type(self)(self.x + other.x)

    def __str__(self) -> str:
        return f'A(x={self.x})'

A1 = A(1)
A2 = A(2)
A3 = A1 + A2
print(A3)

class B(NamedTuple('B', [('x', int)])):

    # The following line will raise an error in mypy
    # error: Argument 1 of "__add__" incompatible with supertype "tuple"
    def __add__(self, other: 'B') -> 'B':
        return type(self)(self.x + other.x)

B1 = B(1)
B2 = B(2)
B3 = B1 + B2
print(B3)

更新:Guido van Rossum 本人已经在 Github 上回答了这个问题

我不是 100% 确定你想要完成什么,但根据你最初的例子,我猜你想为 B 类重新定义 + 以在 B 的实例上实现元素加法。 mypy 的原因没有默认情况下不支持这个被称为“Liskov 替换原则”(你可以谷歌它以获得解释)。

但是有一个解决方法:将 # type: ignore 放在产生错误的行(def add行)上。这听起来不太好,但是只要您从不将 B 实例传递给假定它是元组并尝试对其进行元组连接的代码,它就会做您想做的事情。

4

1 回答 1

1

的类型声明B.__add__表明只有将 的实例添加B到另一个实例才是有效的B(并且实现支持它,因为它期望other.x工作)。但是,该方法覆盖了__add__来自tuple(via namedtuple) 的更通用的方法,该方法可以将任何两个元组连接在一起(因此右侧可以是一个实例tuple或任何子tuple类)。因为您的新方法对其参数有更严格的类型要求,所以您的类(从mypy的角度来看)不是tuple. 您不能将您的类的实例放到以前使用元组的地方,并让它以相同的方式工作。

考虑这个函数:

def tuple_append(tup: tuple, value: any) -> tuple:
     return tup1 + (value,)

这适用于普通元组,但如果您将其中一个B实例作为传递tup,它将失败(即使您tuple按照函数的类型声明要求传递子类的实例)。这就是为什么mypy不认为您的B类型有效的原因。如果它接受了B,其他具有正确类型声明的代码可能会意外中断。

不幸的是,我认为没有解决这个问题的好方法。Python 没有其他一些语言所具有的“私有继承”概念。如果不公开成为子类,就无法从另一个类继承实现。

于 2017-04-17T19:14:34.863 回答