Ошибки MyPy для декоратора с аргументами

#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 Это просто a ValueError и может быть заменено соответствующим образом без какой-либо разницы. Я также отредактировал вопрос, чтобы исправить это.

Ответ №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-строгий с честью.