Есть ли предпочтительный способ создания псевдонимов типов для составных типов с помощью модуля ввода Python? - PullRequest
2 голосов
/ 25 марта 2019

У меня есть функция с одним параметром, которая должна принимать int или None в качестве аргумента. Существует несколько способов создать псевдоним типа для такого составного типа:

# test.py
import typing

IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
IntOrNone_2 = typing.Union[int, None]


def my_func1(xyz: IntOrNone_1):
    return xyz

def my_func2(xyz: IntOrNone_2):
    return xyz


my_func1(12)
my_func1(None)
my_func1(13.7)
my_func1('str')

my_func2(12)
my_func2(None)
my_func2(13.7)
my_func2('str')

Оба метода делают то, что я ожидаю, однако соответствующие ошибки mypy немного отличаются, но в основном имеют одинаковое значение.

test.py: 14: ошибка: значение переменной типа "IntOrNone_1" из "my_func1" нельзя "плавать"

test.py: 15: ошибка: значение переменной типа "IntOrNone_1" из "my_func1" не может быть "str" ​​

test.py: 19: ошибка: аргумент 1 для "my_func2" имеет несовместимый тип «Плавать»; ожидается "Необязательный [int]"

test.py: 20: ошибка: аргумент 1 для "my_func2" имеет несовместимый тип «Ул»; ожидается "Необязательный [int]"

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

Оба метода действительно эквивалентны, как я полагаю, или один из них предпочтительнее?

1 Ответ

3 голосов
/ 25 марта 2019

Два метода очень далеки от эквивалентности. Вы должны избегать думать о TypeVars как о простых псевдонимах - скорее, это более специальные формы, которые вы используете, когда хотите сделать свои функции универсальными .

Проще всего объяснить, что такое "универсальная функция" на примере. Предположим, вы хотите написать функцию, которая принимает некоторый объект (любой объект!) И возвращает другой объект точно такого же типа . Как бы вы это сделали?

Один из способов, который мы могли бы сделать, это попробовать object:

def identity(x: object) -> object:
    return x

Это сближает нас, поскольку наша identity функция может, по крайней мере, принимать буквально все что угодно (поскольку все типы наследуются от object в Python). Однако это решение имеет недостатки: если мы передадим int, мы получим обратно object, а это не то, что мы хотим.

Скорее нам нужен способ проверки типов, чтобы понять, что между этими двумя типами есть «связь». Именно здесь TypeVar пригодится:

T = TypeVar('T')

def identity(x: T) -> T:
    return x

Наш TypeVar 'T' в этом случае действует как "заполнитель", который может быть связан с любым типом, который мы хотим. Поэтому, если мы сделаем identity(3), T будет привязан к int - поэтому средство проверки типов поймет, что тип возвращаемого значения также должен быть int!

И если мы будем использовать T несколько раз в наших подсказках типов параметров, средство проверки типов будет следить за тем, чтобы типы всегда были одинаковыми.


Итак, что делает приведенное ниже выражение?

IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)

Оказывается, иногда полезно добавить ограничений к нашему специальному типу заполнителя. Например, вы ограничили IntOrNone_1, чтобы его можно было связать только с int или None, а не с другими типами.


И, наконец, чтобы ответить на ваш последний вопрос: в приведенных вами примерах вы обязательно должны использовать Union, а не TypeVars.

Независимо от того, используете ли вы Union или псевдоним типа для Union, это действительно вопрос личного вкуса, но если вам не нужно это "заполнитель" или "универсальное" поведение, вам не следует использовать TypeVars.

В mypy docs есть раздел об общих шаблонах , если вы хотите узнать больше о том, как использовать TypeVars. Он охватывает несколько вещей, которые я пропустил, в том числе как создавать универсальные классы (а не только универсальные функции).

...