如何实现对从mypy中的NamedTuple继承的类的方法进行类型检查?

问题描述:

根据mypy文档,如果一个类需要引用它自己,它可以使用forward-reference如何实现对从mypy中的NamedTuple继承的类的方法进行类型检查?

这对于普通的类似乎很好,但我无法使用从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自己回答了this question on Github

我不是100%确定你想要完成什么,但根据你最初的例子,我猜你想重新定义+为B类实现元素明智的添加B. mypy默认不支持这个的原因是所谓的“Liskov替代原则”(你可以通过谷歌来解释)。

但是有一个解决方法:在产生错误的行上输入#type:ignore(def add line)。这听起来不太合适,但只要你不将一个B实例传递给假定它是一个元组的代码并尝试连接它就可以完成你想要的任务。

+0

这很奇怪。请注意,'B类'已经从'__name__''B'类继承...也许这是在搞乱事情。所以'__mro__'会变成类似'()' –

+0

尝试更改'B'的名字,或者你传给'namedtuple'的名字。所以像'class B(NamedTuple('SuperB',[('x',int)])): ' –

+0

这可能是相关的:https://github.com/python/mypy/issues/1237 –

B.__add__的类型声明表明,它是唯一有效的给B一个实例添加到的B另一个实例(和执行备份,最多,因为它预计other.x工作)。但是,该方法将覆盖来自tuple(通过namedtuple)的更一般的__add__方法,该方法可以将任何两个元组连接在一起(因此右侧可以是tuple的实例或任何tuple子类)。因为你的新方法对于它的论点有一个更严格的类型要求,所以你的课程(从mypy的角度来看)不是tuple的适当子类。你不能把你的类的一个实例放到以前使用元组的地方,并且以同样的方式工作。

考虑一下这个功能:

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

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

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