1 回答

TA貢獻1816條經驗 獲得超4個贊
我認為這里的問題在于,當您構建類型別名時,您實際上并不是在構建新類型——您只是為現有類型提供一個昵稱或替代拼寫。
如果您所做的只是為類型提供替代拼寫,則意味著在這樣做時應該不可能添加任何額外的行為。這正是這里發生的事情:您正在嘗試向 Iterable 添加其他信息(您的三種類型約束),而 mypy 忽略了它們。在關于泛型類型別名的mypy 文檔的底部有一個注釋基本上說明了這一點。
事實上,mypy 只是默默地使用你的 TypeVar 而沒有警告它的附加約束被忽略的事實感覺就像一個錯誤。具體來說,它感覺像是一個可用性錯誤:Mypy 應該在這里發出警告,并禁止在類型別名中使用除不受限制的類型變量之外的任何其他內容。
那么你能做些什么來輸入你的代碼呢?
嗯,一個干凈的解決方案是不打擾創建Vector類型別名——或者創建它,但不用擔心限制它可以參數化的內容。
這意味著用戶可以創建一個Vector[str](又名 an Iterable[str]),但這真的沒什么大不了的:當他們嘗試將它實際傳遞給任何函數時,他們會得到一個類型錯誤,比如你的dot_product函數,它確實使用了類型別名。
第二種解決方案是創建自定義vector子類。如果這樣做,您將創建一個新類型,因此實際上可以添加新約束——但您將不再能夠將列表等直接傳遞到您的dot_product類中:您需要將它們包裝在您的自定義中向量類。
這可能有點笨拙,但無論如何您最終可能會轉向這個解決方案:它使您有機會向新的 Vector 類添加自定義方法,這可能有助于提高代碼的整體可讀性,具體取決于您的具體內容正在做。
第三個也是最后一個解決方案是定義一個自定義的“矢量”協議。這將使我們不必將我們的列表包裝在某個自定義類中——并且我們正在創建一個新類型,以便我們可以添加我們想要的任何約束。例如:
from typing import Iterable, TypeVar, Iterator, List
from typing_extensions import Protocol
T = TypeVar('T', int, float, complex)
# Note: "class Vector(Protocol[T])" here means the same thing as
# "class Vector(Protocol, Generic[T])".
class Vector(Protocol[T]):
# Any object that implements these three methods with a compatible signature
# is considered to be compatible with "Vector".
def __iter__(self) -> Iterator[T]: ...
def __getitem__(self, idx: int) -> T: ...
def __setitem__(self, idx: int, val: T) -> None: ...
def dot_product(a: Vector[T], b: Vector[T]) -> T:
return sum(x * y for x, y in zip(a, b))
v1: Vector[int] = [] # OK: List[int] is compatible with Vector[int]
v2: Vector[float] = [] # OK: List[float] is compatible with Vector[int]
v3: Vector[str] = [] # Error: Value of type variable "T" of "Vector" cannot be "str"
dot_product(v3, v3) # Error: Value of type variable "T" of "dot_product" cannot be "str"
nums: List[int] = [1, 2, 3]
dot_product(nums, nums) # OK: List[int] is compatible with Vector[int]
這種方法的主要缺點是您無法真正向您的協議添加任何具有實際邏輯的方法,您可以在任何可能被視為“向量”的東西之間重用這些方法。(好吧,您可以,但不會以任何方式在您的示例中有用)。
添加回答
舉報