【问题标题】:MyPy Errors for Decorator w/ Arguments带参数的装饰器的 MyPy 错误
【发布时间】:2021-10-31 18:24:03
【问题描述】:

我正在尝试使用 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 Playgroundhere 上重现。

【问题讨论】:

  • @AlexWaygood 感谢您的回复!所以实际上不是装饰器定义的问题,而是装饰器与另一个函数的定义一起使用时。我已经编辑了我的问题以显示这一点(可以在操场上重现)。
  • @AlexWaygood ReadDataError 只是一个ValueError,可以相应地替换,没有区别。我还编辑了问题来解决这个问题。

标签: python python-decorators type-hinting mypy python-typing


【解决方案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]

重要的是要认识到,不是接受参数的装饰器正在做的事情。 multi_try_if_error 不是接收F 类型的函数并吐出F 类型的新函数(可以概念化为Callable[[F], F]),而是一个接收两个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。进行此更改后,您的装饰器passes MyPy --strict 就会出彩。

【讨论】:

    猜你喜欢
    • 2018-01-07
    • 2022-12-28
    • 2014-07-21
    • 1970-01-01
    • 2021-11-22
    • 2019-11-13
    • 2021-05-09
    • 2014-10-14
    相关资源
    最近更新 更多