Почему mypy игнорирует переменную общего типа, которая содержит тип, несовместимый с TypeVar? - PullRequest
4 голосов
/ 27 марта 2019

Ниже я определяю переменную типа, псевдоним универсального типа и функцию точечного произведения.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?

1 Ответ

1 голос
/ 27 марта 2019

Я думаю, что проблема в том, что когда вы создаете псевдоним типа, вы на самом деле не создаете новый тип - вы просто присваиваете псевдоним или альтернативное написание существующему.

И если все, что вы делаете, - это предоставление альтернативного написания типу, это означает, что при этом невозможно добавить какое-либо дополнительное поведение при этом. Это именно то, что здесь происходит: вы пытаетесь добавить дополнительную информацию (ваши три ограничения типа) в 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]

Основным недостатком этого подхода является то, что вы не можете реально добавить в протокол какие-либо методы с реальной логикой, которые вы можете использовать повторно между всем, что можно считать «вектором». (Ну, вы вроде как можете, но не в любом случае, что будет полезно в вашем примере).

...