Я думаю, что проблема в том, что когда вы создаете псевдоним типа, вы на самом деле не создаете новый тип - вы просто присваиваете псевдоним или альтернативное написание существующему.
И если все, что вы делаете, - это предоставление альтернативного написания типу, это означает, что при этом невозможно добавить какое-либо дополнительное поведение при этом. Это именно то, что здесь происходит: вы пытаетесь добавить дополнительную информацию (ваши три ограничения типа) в Iterable, а mypy игнорирует их. В нижней части mypy docs для псевдонимов универсального типа .
есть примечание.
Тот факт, что mypy просто тихо использует ваш TypeVar без предупреждения о том, что его дополнительные ограничения игнорируются, на самом деле выглядит как ошибка. В частности, это похоже на ошибку юзабилити: Mypy должен был выдать предупреждение и запретить использовать что-либо, кроме неограниченных typevars внутри вашего псевдонима типа.
Так что вы можете сделать, чтобы набрать код?
Что ж, одним чистым решением было бы не беспокоиться о создании псевдонима типа Vector
или создавать его, но не беспокоиться об ограничении того, с чем его можно параметризировать.
Это означает, что пользователь может создать Vector[str]
(он же Iterable[str]
), но в этом нет ничего особенного: он получит ошибку типа в тот момент, когда попытается фактически передать его в любую функцию, такую как dot_product
. функция, которая делает использование псевдонимов типа.
Вторым решением будет создание пользовательского подкласса vector
. Если вы сделаете это, вы создадите новый тип, и сможет фактически добавить новые ограничения - но вы больше не сможете передавать списки и тому подобное непосредственно в ваши dot_product
классы: вы нужно обернуть их в свой собственный класс Vector.
Это может показаться немного неуклюжим, но в любом случае вы, возможно, в конечном итоге дрейфуете к этому решению: оно дает вам возможность добавлять пользовательские методы в ваш новый класс 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]
Основным недостатком этого подхода является то, что вы не можете реально добавить в протокол какие-либо методы с реальной логикой, которые вы можете использовать повторно между всем, что можно считать «вектором». (Ну, вы вроде как можете, но не в любом случае, что будет полезно в вашем примере).