带有参数的装饰器的 MyPy 错误
MyPy Errors for Decorator w/ Arguments
我正在尝试使用 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 playground 上重现 here。
这里的问题是装饰器外层的 return 类型。您需要将类型注释更正为以下内容:
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
的函数并吐出类型 F
的新函数(可以概念化为 Callable[[F], F]
),而是 multi_try_if_error
是接受类型的函数两个 int
参数和 returns 一个函数,它将接收 F
类型的函数和 return 类型 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]]
因此,装饰器的外层必须注释为 returning Callable[[F], F]
而不是 returning F
。进行此更改后,您的装饰器 passes MyPy --strict 会大放异彩。
对于使用 Python >= 3.10 can be written a bit more succinct with the help of the new parameter specification variables 的人。
引用docs,这可以帮助我们解决“遗留”解决方案的以下问题:
- The type checker can’t type check the inner function because
*args
and **kwargs
have to be typed Any
.
cast()
may be required in the body of the add_logging decorator when returning the inner function, or the static type checker must be told to ignore the return inner
.
from time import sleep
from typing import Callable, ParamSpec, TypeVar
T = TypeVar("T")
P = ParamSpec("P")
def multi_try_if_error(
n_tries: int, sleep_duration: int
) -> Callable[[Callable[P, T]], Callable[P, T]]:
def decorator(fn: Callable[P, T]) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
for _ in range(n_tries):
try:
return fn(*args, **kwargs)
except Exception as e:
caught = e
sleep(sleep_duration)
raise ValueError(caught)
return wrapper
return decorator
我正在尝试使用 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 playground 上重现 here。
这里的问题是装饰器外层的 return 类型。您需要将类型注释更正为以下内容:
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
的函数并吐出类型 F
的新函数(可以概念化为 Callable[[F], F]
),而是 multi_try_if_error
是接受类型的函数两个 int
参数和 returns 一个函数,它将接收 F
类型的函数和 return 类型 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]]
因此,装饰器的外层必须注释为 returning Callable[[F], F]
而不是 returning F
。进行此更改后,您的装饰器 passes MyPy --strict 会大放异彩。
对于使用 Python >= 3.10
引用docs,这可以帮助我们解决“遗留”解决方案的以下问题:
- The type checker can’t type check the inner function because
*args
and**kwargs
have to be typedAny
.cast()
may be required in the body of the add_logging decorator when returning the inner function, or the static type checker must be told to ignore thereturn inner
.
from time import sleep
from typing import Callable, ParamSpec, TypeVar
T = TypeVar("T")
P = ParamSpec("P")
def multi_try_if_error(
n_tries: int, sleep_duration: int
) -> Callable[[Callable[P, T]], Callable[P, T]]:
def decorator(fn: Callable[P, T]) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
for _ in range(n_tries):
try:
return fn(*args, **kwargs)
except Exception as e:
caught = e
sleep(sleep_duration)
raise ValueError(caught)
return wrapper
return decorator