Как я могу гарантировать, что аргументы имеют один и тот же тип, не перечисляя типы явно?

#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 здесь полезно.