Как динамически использовать подсказку типа Python, чтобы указать возвращаемое значение, совпадающее с типом параметра - PullRequest
1 голос
/ 11 февраля 2020

Я использую Python для чтения JSON с диска, и я пытаюсь убедиться, что мои подсказки по типу верны в нисходящем направлении. Например, что-то вроде этого:

from typing import List

def verify_contains_ints(items: List[object]) -> List[int]:
    for item in items:
        if not isinstance(item, int):
            raise TypeError("List contents must be all ints")
    return items

Проблема, с которой я сталкиваюсь, заключается в том, что я не хочу писать отдельные функции для int, bool, str, et c. Есть ли способ динамически указать тип, который я хочу проверить? Что бы я хотел, чтобы вроде было написано примерно так:

from typing import List

def verify_contains_type(items: List[object], inner_type = object) -> List[inner_type]:
    for item in items:
        if not isinstance(item, inner_type):
            raise TypeError(f"List contents must be all {inner_type}s")
    return items

Есть ли способ сделать это в текущем состоянии подсказки типа?

Примечание Это упрощенная версия того, что я на самом деле пытаюсь сделать. Внутренний тип по умолчанию может показаться глупым, но это важно для моего варианта использования.

Ответы [ 3 ]

1 голос
/ 11 февраля 2020

Полагаю, вы можете использовать typing.cast, что немного уродливо. Обратите внимание, что он не имеет никаких эффектов времени выполнения, он просто возвращает то, что было передано в него, хотя он вызывает накладные расходы при вызове функции. Но он сообщает средству проверки типа «это сейчас такого типа». Вы должны использовать TypeVar, чтобы сделать его родовым c, а затем просто передать тип, как вы пытались сделать, и аннотировать его с помощью Type

from typing import List, TypeVar, Type, cast

T = TypeVar('T')
def verify_contains_type(items: List[object], inner_type: Type[T]) -> List[T]:
    for item in items:
        if not isinstance(item, inner_type):
            raise TypeError("List contents must be all ints")
    return cast(List[T], items)


mylist: List[object] = [1, 2, 3, 4]

safe_mylist: List[int] = verify_contains_type(mylist, int)
print(safe_mylist[0] + safe_mylist[1])

mypy теперь счастлив:

(py38) juan$ mypy --version
mypy 0.750
(py38) juan$ mypy test_typing.py
Success: no issues found in 1 source file
0 голосов
/ 23 февраля 2020

Чтобы добавить к ответ juanpa.arrivillaga , но с поддержкой по умолчанию inner_type из object. Лучший способ найти комбинацию typing.Union и typing.overload. Должен признаться, это довольно многословно, но, как минимум, никаких функциональных изменений в коде не требуется.

Решение

from typing import List, Type, TypeVar, Union, cast, overload, 

T = TypeVar('T')


@overload
def verify_contains_type(items: List[object], inner_type: Type[T]) -> List[T]:
    ...

@overload
def verify_contains_type(
    items: List[object], inner_type: Type[object] = object
) -> List[object]:
    ...

def verify_contains_type(
    items: List[object], inner_type: Union[Type[T], Type[object]] = object
) -> Union[List[T], List[object]]:
    for item in items:
        if not isinstance(item, inner_type):
            raise TypeError(f"List contents must be all {inner_type!r}")
    return cast(List[T], items)

После этого:

mylist: List[object] = [1, 2, 3, 4]

# Revealed type is 'builtins.list[builtins.float*]'
my_floats = verify_contains_type(mylist, float)


# Revealed type is 'builtins.list[builtins.object]'
my_whatevers = verify_contains_type(mylist)

Объяснение

При анализе использования функции средство проверки типов будет рассматривать только определения функции @overload, проверяя их в указанном порядке, пока не будет найдено соответствие. Аннотации типов в фактической функции учитываются , а не .

При анализе кода внутри тела самой функции при проверке типа будет использоваться только аннотация типа фактической функции. и игнорируйте определения @overload.

Дополнительная информация о @overload:

0 голосов
/ 11 февраля 2020

Да, но только если вы в порядке с созданием нового списка:

from typing import List, Type, TypeVar

T = TypeVar('T')

def verify_contains(items: List[object], inner_type: Type[T]) -> List[T]:
    # Mypy currently needs this hint to infer what 'clean_items' is
    # supposed to contain. Other type checkers may not.
    clean_items: List[T] = []
    for item in items:
        if not isinstance(item, inner_type):
            raise TypeError("List contents must be all ints")
        clean_items.append(item)
    return clean_items

Если вы не знаете, что такое TypeVars, они позволяют вам написать generi c код. См. https://mypy.readthedocs.io/en/stable/generics.html для более подробной информации.

Тип позволяет вам указать, что вы хотите объект класса, а не экземпляр класса. См. https://www.python.org/dev/peps/pep-0484/#the -type-of-class-objects для получения более подробной информации.


Причина, по которой нам нужно создать новый список, заключается в том, что если мы этого не сделаем, вы может привести к ошибке в вашем коде из-за мутации. Например:

original: List[object] = [3, 2, 1]
all_ints = verify_contains(original, int)

# Legal, since strs are a kind of object
original.append("boom")

# ...but if verify_contains doesn't return a copy, this will
# print out [3, 2, 1, "boom"]!
print(all_ints)

Если вы можете игнорировать эту потенциальную ошибку, используйте приведение, как это предлагается в некоторых других ответах.


Еще один альтернативный подход может быть просто использовать библиотеку, такую ​​как Pydanti c вместо того, чтобы писать эту логику проверки c самостоятельно.

Лично я бы выбрал такой подход: я могу сосредоточиться только на написании выровнять схемы, используя типы PEP 484, и позволить библиотеке обрабатывать проверку для меня.

...