下面我定义类型变量,泛型类别别名和点积函数。 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而没有警告其附加约束被忽略的事实感觉就像一个bug。具体来说,感觉就像是一个可用性错误:Mypy应该在这里发出警告并禁止在你的类型别名中使用除了不受限制的typevars之外的任何东西。
那么你可以做些什么来输入你的代码呢?
好吧,一个干净的解决方案是不打扰创建Vector
类型别名 - 或创建它,但不担心限制它可以参数化的内容。
这意味着用户可以创建一个Vector[str]
(又名Iterable[str]
),但这真的没什么大不了的:当他们尝试将它实际传递给任何函数时,他们会得到一个类型错误,比如使用类型别名的dot_product
函数。
第二种解决方案是创建自定义vector
子类。如果你这样做,你就会创建一个新类型,所以实际上可以添加新的约束 - 但你不能再将列表等直接传递到你的dot_product
类中:你需要将它们包装在你的自定义Vector类。
这可能有点笨拙,但你可能最终会漂移到这个解决方案:它让你有机会为你的新Vector类添加自定义方法,这可能有助于提高代码的整体可读性,具体取决于你究竟是什么'干嘛。
第三个也是最后一个解决方案是定义一个自定义的“矢量”Protocol。这将让我们避免必须将我们的列表包装在一些自定义类中 - 我们正在创建一个新类型,以便我们可以添加我们想要的任何约束。例如:
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”的东西之间重用它们。 (嗯,你可以,但不能以任何方式在你的例子中有用)。