为什么mypy忽略包含类型与TypeVar不兼容的泛型变量?

Tom*_*ips 5 python types type-hinting mypy

在下面,我定义类型变量,泛型类型别名和点积函数。mypy不会引发错误。为什么不?

我希望它抛出一个错误v3,因为它是一个字符串矢量,我已经明确指出T必须是intfloatcomplex

from typing import Any, Iterable, Tuple, TypeVar

T = TypeVar('T', int, float, complex)
Vector = Iterable[T]

def dot_product(a: Vector[T], b: Vector[T]) -> T:
    return sum(x * y for x, y in zip(a, b))

v1: Vector[int] = []    # same as Iterable[int], OK
v2: Vector[float] = []  # same as Iterable[float], OK
v3: Vector[str] = []    # no error - why not?
Run Code Online (Sandbox Code Playgroud)

Mic*_*x2a 4

我认为这里的问题是,当您构造类型别名时,您实际上并没有构造一种新类型 - 您只是为现有类型提供昵称或替代拼写。

如果您所做的只是为类型提供替代拼写,则意味着在这样做时应该不可能添加任何额外的行为。这正是这里发生的情况:您尝试向 Iterable 添加附加信息(您的三种类型约束),而 mypy 忽略它们。mypy 文档底部关于泛型类型别名的注释基本上说明了这一点。

实际上,mypy 只是默默地使用您的 TypeVar,而没有警告其附加约束被忽略,这一事实感觉就像一个错误。具体来说,这感觉像是一个可用性错误:Mypy 应该在此处发出警告,并禁止在类型别名中使用除不受限制的类型变量之外的任何其他内容。


那么你可以做什么来输入代码呢?

好吧,一种干净的解决方案是不费心创建Vector类型别名——或者创建它,但不用担心限制它可以参数化的内容。

这意味着用户可以创建 a 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]
Run Code Online (Sandbox Code Playgroud)

这种方法的主要缺点是,您无法真正将任何具有实际逻辑的方法添加到协议中,以便您可以在可能被视为“向量”的任何内容之间重用。(嗯,你可以,但在你的例子中没有任何用处)。