#python #python-decorators #type-hinting #mypy #python-typing
Вопрос:
Я пытаюсь выполнить строгую проверку типов с помощью MyPy и, похоже, не могу заставить декораторов с аргументами работать без возникновения конфликтующих ошибок.
Согласно документам и другим ответам, я думаю, что у вас должно быть что-то вроде этого:
from time import sleep
from typing import TypeVar, Any, Callable, cast
F = TypeVar('F', bound=Callable[..., Any])
def multi_try_if_error(n_tries: int, sleep_duration: int) -> F:
def decorator(fn: F) -> F:
def wrapper(*args, **kwargs):
for _ in range(n_tries):
try:
return fn(*args, **kwargs)
except Exception as e:
caught = e
sleep(sleep_duration)
raise ValueError(caught)
return cast(F, wrapper)
return cast(F, decorator)
# example usage — this is where the error is raised
@multi_try_if_error(n_tries=3, sleep_duration=1)
def query_db(q: str) -> None:
return
Но это приводит к следующим ошибкам:
lib/decorator_defined.py:32: error: Function is missing a type annotation
lib/decorator_used.py:23: error: Untyped decorator makes function "query_db" untyped
lib/decorator_used.py:23: error: <nothing> not callable
Даже если это не то, что предлагают документы, я могу изменить определение оболочки на:
def wrapper(*args: Any, **kwargs: Any) -> Any:
for _ in range(n_tries):
...
Что устраняет первую ошибку, но у меня все еще есть две другие ошибки декоратора везде, где используется декоратор.
Есть идеи, как это исправить?
Ошибку можно воспроизвести на игровой площадке Mypy здесь.
Комментарии:
1. @AlexWaygood Спасибо за ответ! Так что на самом деле это не проблема с определением декоратора, но когда декоратор используется с определением другой функции. Я отредактировал свой вопрос, чтобы показать это (что воспроизводимо на игровой площадке).
2. @AlexWaygood
ReadDataError
Это просто aValueError
и может быть заменено соответствующим образом без какой-либо разницы. Я также отредактировал вопрос, чтобы исправить это.
Ответ №1:
Проблема здесь заключается в типе возврата внешнего слоя вашего декоратора. Вам необходимо исправить аннотацию типа следующим образом:
from time import sleep
from typing import TypeVar, Any, Callable, cast
F = TypeVar('F', bound=Callable[..., Any])
def multi_try_if_error(n_tries: int, sleep_duration: int) -> Callable[[F], F]:
def decorator(fn: F) -> F:
def wrapper(*args: Any, **kwargs: Any) -> Any:
for _ in range(n_tries):
try:
return fn(*args, **kwargs)
except Exception as e:
caught = e
sleep(sleep_duration)
raise ValueError(caught)
return cast(F, wrapper)
return decorator
@multi_try_if_error(n_tries=3, sleep_duration=1)
def query_db(q: str) -> None:
return
Что здесь происходит
Концептуализация (и намек на тип) простого декоратора, который не принимает аргументы, довольно прямолинейна. Мы определяем функцию C
, которая принимает функцию типа F
и выплевывает новую функцию, которая также имеет тип F
.
# Decorator that doesn't take arguments takes in a function,
# and spits out a function of the same type
DecoratorTypeNoArgs = Callable[[F], F]
Однако важно признать, что декоратор, принимающий аргументы, делает не это. Вместо того, чтобы принимать функцию типа F
и выплевывать новую функцию типа F
(которая может быть понята как Callable[[F], F]
), multi_try_if_error
это функция, которая принимает два int
аргумента и возвращает функцию, которая будет принимать функцию типа F
и возвращать функцию типа F
(которая может быть понята как Callable[[int, int], Callable[[F], F]]
).
# Decorator that takes arguments takes in arguments,
# and spits out a decorator that doesn't take arguments
DecoratorTypeWithArgs = Callable[[int, int], Callable[[F], F]]
Таким образом, внешний слой вашего декоратора должен быть помечен как возвращающийся Callable[[F], F]
, а не как возвращающийся F
. Как только вы внесете это изменение, ваш декоратор пройдет MyPy-строгий с честью.