#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
Комментарии:
1. в чем разница с
typeguard
тем, что уже использует OP?2. он прост в использовании. в других темах, я думаю
typeguard
, лучше, но здесь я использую это.3. OP хочет вводить переменные проверки, аннотированные внутри функций, а не то, что они входят или выходят из одного,
typeguard
уже делают это4. отредактированный ответ. пожалуйста, проверьте это еще раз
5. Я думаю, что typeguard тоже может это сделать, он хочет, чтобы проверка выполнялась ВНУТРИ функции для произвольной переменной внутри нее, которая была аннотирована