为什么 mypy 会忽略包含与 TypeVar 不兼容的类型的泛型变量?
Why does mypy ignore a generic-typed variable that contains a type incompatible with the TypeVar?
下面我定义了类型变量、泛型类型别名和一个点积函数。 mypy
不会引发错误。为什么不呢?
我希望它会引发 v3
的错误,因为它是一个字符串向量,我已经指定 T
必须是 int
、float
, 或 complex
.
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?
我认为这里的问题是,当您构造类型别名时,您实际上并没有构造一个新类型——您只是给现有类型一个昵称或替代拼写。
如果您所做的只是为一种类型提供替代拼写,这意味着在这样做时应该不可能添加任何额外的行为。这正是这里发生的事情:您试图向 Iterable 添加附加信息(您的三种类型约束),而 mypy 忽略了它们。在 mypy docs on generic type aliases.
的底部有一条注释基本上是这样说的
事实上,mypy 只是静静地使用你的 TypeVar,而没有警告它的附加约束被忽略,这感觉就像一个错误。具体来说,这感觉像是一个可用性错误:Mypy 应该在这里发出警告,并禁止在您的类型别名中使用除不受限制的类型变量之外的任何其他内容。
那么您可以怎样输入代码?
嗯,一个干净的解决方案是不创建 Vector
类型别名——或者创建它,但不必担心限制它可以参数化的内容。
这意味着用户可以创建一个 Vector[str]
(又名 Iterable[str]
),但这真的没什么大不了的:当他们尝试将它实际传递给任何功能类似于 使用类型别名的 dot_product
函数。
第二种解决方案是创建自定义 vector
subclass。如果这样做,您将创建一个新类型,因此 可以 实际上添加新的约束——但您将无法再将列表等直接传递到您的 dot_product
classes:您需要将它们包装在自定义 Vector class.
中
这可能有点笨拙,但无论如何您最终可能会转向这个解决方案:它让您有机会向新 Vector class 添加自定义方法,这可能有助于提高整体可读性您的代码,具体取决于您在做什么。
第三个也是最后一个解决方案是定义自定义 "Vector" Protocol。这将使我们避免必须将我们的列表包装在一些自定义 class 中——我们正在创建一个新类型,以便我们可以添加我们想要的任何约束。例如:
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]
这种方法的主要缺点是您无法真正将任何具有实际逻辑的方法添加到您的协议中,您可以在可能被视为 "Vector" 的任何事物之间重复使用这些方法。 (好吧,你可以,但不是以任何方式对你的例子有用)。
下面我定义了类型变量、泛型类型别名和一个点积函数。 mypy
不会引发错误。为什么不呢?
我希望它会引发 v3
的错误,因为它是一个字符串向量,我已经指定 T
必须是 int
、float
, 或 complex
.
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?
我认为这里的问题是,当您构造类型别名时,您实际上并没有构造一个新类型——您只是给现有类型一个昵称或替代拼写。
如果您所做的只是为一种类型提供替代拼写,这意味着在这样做时应该不可能添加任何额外的行为。这正是这里发生的事情:您试图向 Iterable 添加附加信息(您的三种类型约束),而 mypy 忽略了它们。在 mypy docs on generic type aliases.
的底部有一条注释基本上是这样说的事实上,mypy 只是静静地使用你的 TypeVar,而没有警告它的附加约束被忽略,这感觉就像一个错误。具体来说,这感觉像是一个可用性错误:Mypy 应该在这里发出警告,并禁止在您的类型别名中使用除不受限制的类型变量之外的任何其他内容。
那么您可以怎样输入代码?
嗯,一个干净的解决方案是不创建 Vector
类型别名——或者创建它,但不必担心限制它可以参数化的内容。
这意味着用户可以创建一个 Vector[str]
(又名 Iterable[str]
),但这真的没什么大不了的:当他们尝试将它实际传递给任何功能类似于 使用类型别名的 dot_product
函数。
第二种解决方案是创建自定义 vector
subclass。如果这样做,您将创建一个新类型,因此 可以 实际上添加新的约束——但您将无法再将列表等直接传递到您的 dot_product
classes:您需要将它们包装在自定义 Vector class.
这可能有点笨拙,但无论如何您最终可能会转向这个解决方案:它让您有机会向新 Vector class 添加自定义方法,这可能有助于提高整体可读性您的代码,具体取决于您在做什么。
第三个也是最后一个解决方案是定义自定义 "Vector" Protocol。这将使我们避免必须将我们的列表包装在一些自定义 class 中——我们正在创建一个新类型,以便我们可以添加我们想要的任何约束。例如:
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]
这种方法的主要缺点是您无法真正将任何具有实际逻辑的方法添加到您的协议中,您可以在可能被视为 "Vector" 的任何事物之间重复使用这些方法。 (好吧,你可以,但不是以任何方式对你的例子有用)。