2017-03-03 53 views
1

根據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 –

回答

1

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沒有一些其他語言所做的「私有繼承」的概念。如果沒有公開成爲子類,則無法繼承其他類的實現。

相關問題