#python #type-hinting #mypy #python-typing
#python #подсказка типа #mypy #python-типизация
Вопрос:
Предположим, что нам нужна функция, которая принимает два аргумента любого типа, если оба аргумента имеют один и тот же тип. Как бы вы проверили это статически с помощью mypy?
Если нам нужно, чтобы функция принимала только некоторое конечное количество уже известных типов, это легко:
from typing import TypeVar, List, Callable
T = TypeVar('T', int, str, List[int], Callable[[], int])
def f(a: T, b: T) -> None:
pass
f(1, 2)
f("1", "2")
f([1], [2])
f(lambda: 1, lambda: 2)
f(1, "2") # mypy will print an error message
Для этого кода mypy может гарантировать, что аргументы to f
являются либо двумя int
s, либо двумя str
s, либо двумя списками int
s, либо двумя функциями с нулевыми аргументами, которые возвращаются int
.
Но что, если мы не знаем типы заранее? Что, если нам нужно что-то похожее на let f (a:'t) (b:'t) = ()
from F # и OCaml? Простое написание T = TypeVar('T')
сделало бы такие вещи, как f(1, "2")
valid, а это не то, что мы хотим.
Ответ №1:
То, о чем вы просите, невозможно (см. Объяснение Ниже). Но обычно в python нет необходимости требовать, чтобы два аргумента имели точно идентичный тип.
В вашем примере, int
, str
, List[int]
, Callable[[], int]
нет никаких общих методов или атрибутов (кроме тех, которые есть у любых двух object
экземпляров), поэтому, если вы не проверите тип вручную isinstance
, вы не сможете сделать с вашим аргументом ничего, что вы не могли бы сделать с object
экземплярами. Не могли бы вы объяснить свой вариант использования?
Объяснение, почему вы не можете обеспечить равенство типов
Система типов Mypy имеет подтипы. Поэтому, когда вы пишете f(a, b)
, mypy проверяет только, что типы a
и b
оба являются подтипами T
, а не точно равны T
.
Кроме того, система подтипов mypy в основном предопределена и не находится под контролем программиста, в частности, каждый тип является подтипом object
. (IIUC, в OCaml программист должен явно указать, какие типы должны находиться в отношениях подтипов, поэтому по умолчанию каждое ограничение типа является ограничением равенства. Вот почему вы могли делать то, что хотели в OCaml).
Итак, когда вы пишете
T = TypeVar('T')
f(a: T, b: T) -> None: ...
f(x, y)
вы только сообщаете mypy, что типы x
и y
должны быть подтипами некоторого общего типа T
. И, конечно, это ограничение всегда (тривиально) выполняется путем вывода, что T
есть object
.
Обновить
На ваш вопрос в комментарии (возможно ли гарантировать, что type of y
относится к подтипу type of x
?), Ответ также отрицательный.
Несмотря mypy
на то, что переменная типа может быть ограничена сверху указанным типом, эта привязка не может быть другой переменной типа, поэтому это не сработает:
T = TypeVar('T')
U = TypeVar('U', bound=T, contravariant=True) # error, T not valid here
f(x: T, y: U) -> None
Комментарии:
1. Я вижу. Действительно, mypy делает здесь именно то, что я прошу его сделать: 1 и «2» на самом деле имеют один и тот же тип в его понимании, потому что оба
int
иstr
являются подтипамиobject
. Есть ли способ заставить mypy гарантировать, что в вызове подобныйf(a: T, b: S)
типS
является подтипомT
?2. Тогда для чего нужны переменные простого типа в подсказке типа в Python? Они говорят нам очень мало. Мы могли бы просто использовать одну переменную типа везде.
3. @Narfanar Вы можете увидеть несколько примеров того, почему
TypeVar
здесь полезно.