Проверка типов встроенных подсказок типа в Python

#python #decorator #type-hinting #typeguards

Вопрос:

В настоящее время я использую typeguard.typechecked для оформления своих функций и проверки входных и выходных данных важных функций.

 from typeguard import typechecked
@typechecked  # Gives me an error if argument/return types don't match the hints
def takes_int_makes_float(x: int) -> float:
    return float(x)
 

Но что, если я хочу проверить типы, поступающие из функций, которые я не могу контролировать, и ошибка, когда тип не тот, который я хочу?

 @proposed_decorator
def does_something_interesting():
    # Implicitly assert that this is the right type.
    # Note that this is valid Python code already, and works as a hint to my IDE.
    x: float = makes_float_or_tuple()
    print(x)
 

Есть ли какой-нибудь способ сделать это? Конечно, это можно реализовать с помощью декоратора, используя ast .

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

Редактировать:

В ответ на предложение о переносе методов с информацией о вводе я поделюсь своим реальным вариантом использования.

Я использую torchtyping для документирования и обеспечения формы моих тензоров PyTorch.

 from torchtyping import TensorType
import torch

def interesting_reshaping_method(x: TensorType['batch', 'num_points', 'point_dim']):
    lengths: TensorType['batch', 'num_points', 1] = torch.norm(x, dim=2, keepdim=True)
    # ... do something that requires this exact shape to do what I want,
    # but will fail silently if the shape isn't what I want.
 

В этом случае мне нужно явно проверить тип тензора, и его форма была бы другой, если бы, например, я использовал keepdim=False или какой-то другой dim . Он также должен быть кратким, чтобы я мог использовать его в качестве документации, а также для обнаружения ошибок там, где они действительно происходят.

Комментарии:

1. «Явное лучше, чем неявное». ~ python’s zen.

2. и почему бы вам просто не создать оформленную версию тех функций, которые вы хотите проверить?

3. @Copperfield Я очень четко говорю о желаемом поведении, я просто не хочу вводить тонны шаблонов. Причина, по которой декоратор не используется для функции, которую я хочу вызвать, заключается в том, что библиотечные функции часто не имеют подсказок по типу. Что-то вроде typecheck_inline флага для typechecked декоратора было бы совершенно ясно.

Ответ №1:

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

Затем просто добавьте к ним подсказки типа.

Существует как минимум 2 способа сделать это.

Первый вариант — создать новую функцию с единственной целью добавить к ней подсказку типа, которую вы хотите, например:

 >>> from typeguard import typechecked
>>> from typing import Optional, Tuple, get_type_hints
>>> import mimetypes #our guinea pig for the example :p
>>> get_type_hints(mimetypes.guess_type)
{}
>>> @typechecked
def guess_type_bad(*a,**k) -> Tuple[str,Optional[int]]: #deliberately bad to see that it works
    return mimetypes.guess_type(*a,**k)

>>> guess_type_bad("test.txt")
('text/plain', None)
>>> guess_type_bad("test.tar.gz")
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    guess_type_bad("test.tar.gz")
  File "C:Python39libsite-packagestypeguard__init__.py", line 1019, in wrapper
    raise TypeError(*exc.args) from None
TypeError: type of the return value[1] must be one of (int, NoneType); got str instead
>>>
>>> @typechecked
def guess_type_good(*a,**k) -> Tuple[str,Optional[str]]:
    return mimetypes.guess_type(*a,**k)

>>> guess_type_good("test.tar.gz")
('application/x-tar', 'gzip')
>>>
 

второй вариант — напрямую записать в их __annotations__ специальный атрибут, а затем украсить его

(это не будет работать для встроенных функций и / или расширений c, только для тех, которые созданы с помощью чистого python, поэтому вы не можете сделать это, например, с math.cos или numpy.array)

 >>> mimetypes.guess_type.__annotations__["return"]=Tuple[str,Optional[str]]
>>> guess_type = typechecked(mimetypes.guess_type)  #decoration with "@" is just syntactic sugar for this
>>> guess_type("test.tar.gz")
('application/x-tar', 'gzip')
>>> help(guess_type)
Help on function guess_type in module mimetypes:

guess_type(url, strict=True) -> Tuple[str, Optional[str]]
    Guess the type of a file based on its URL.
    
    [....]

>>>     
 

Что касается некоторой встроенной версии, кажется, что это просто невозможно сделать динамически для функций:
7.2.2. Аннотированные операторы присваивания

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

поскольку эта информация теряется во время выполнения, ни один декоратор с любым количеством хакерства не может знать, что данная переменная внутри функции была каким-то образом аннотирована, поэтому это просто артефакт для статических средств проверки типов, которые могут напрямую просматривать исходный код (например, вашу IDE), поэтому вам понадобится компилятор для добавленияэти проверки типов в коде для вас, если вы хотите их использовать во время выполнения… И если вы этого хотите, то вам, вероятно, нужно подумать, не лучше ли вам использовать другой язык, который вместо этого предлагает собственный механизм проверки и применения типов.

Кроме того, используйте одно из решений, которые я предлагаю, или assert по умолчанию isinstance , и т. Д., Которые выглядят как единственный способ

Комментарии:

1. Хм, это хороший подход, когда я хочу добавить информацию о вводе в определенные функции, но я все еще думаю, что мне нужен API, который я описал в своем посте. Я обновил его, чтобы показать мой точный вариант использования, чтобы вы могли понять, почему он не совсем подходит.

2. в вашем редактировании вы указываете «сделать что-то, что зависит от формы тензора», что звучит как задание для диспетчера , если ваша текущая функция представляет собой набор if для проверки формы, вы можете попробовать превратить каждую ветвь в свою собственную функцию и позволить диспетчеру выбрать путь в соответствии с подсказкой типаи если одного в стандартной библиотеке недостаточно, вы всегда можете создать свой собственный, изучив один в стандартной библиотеке

3. Нет, извините, я не имею в виду как оператор if. Я имею в виду, что форма должна быть правильной, чтобы остальная часть функции работала должным образом. Я обновил комментарий в этой функции, чтобы уточнить.

4. можете ли вы включить пример, который действительно работает, чтобы проиллюстрировать пару случаев?, «но произойдет молчаливый сбой, если форма не то, что я хочу». ммм, если вы хотите защищаться, тогда вам нужно четко указать на это и проверить, иначе это зависит от пользователя, если он / она получит что-то ещеза неправильное использование

5. Не нашел хорошего решения для этого, но спасибо за ваши усилия @Copperfield. Я дам вам награду, а затем, вероятно, внесу свой вклад в эту функцию typeguard .

Ответ №2:

Установка:

 pip install runtime-type-checker
 

Описание

Вы можете проверить объект на соответствие типу или аннотации с помощью функции check_type .

Функция возвращает None, если проверка прошла успешно, или выдает TypeError в случае ошибки.

Обратите внимание, что эта функция не проверяет рекурсивно, например, атрибуты класса.

 from typing import List, Sequence, Optional, Mapping
from dataclasses import dataclass
from runtime_type_checker import check_type


check_type("a", str)  # OK
check_type(["a"], List[str])  # OK
check_type(["a", 1], Sequence[str])  # raises TypeError


@dataclass
class Foo:
    a: int
    b: Optional[Mapping[str, int]] = None


check_type(Foo(1), Foo)  # OK
check_type(Foo(1), int)  # raises TypeError
 

ссылка: https://pypi.org/project/runtime-type-checker /

Комментарии:

1. в чем разница с typeguard тем, что уже использует OP?

2. он прост в использовании. в других темах, я думаю typeguard , лучше, но здесь я использую это.

3. OP хочет вводить переменные проверки, аннотированные внутри функций, а не то, что они входят или выходят из одного, typeguard уже делают это

4. отредактированный ответ. пожалуйста, проверьте это еще раз

5. Я думаю, что typeguard тоже может это сделать, он хочет, чтобы проверка выполнялась ВНУТРИ функции для произвольной переменной внутри нее, которая была аннотирована