装饰器的类型注释

Tai*_*ano 10 python

这不是一个大问题,但我只是想知道解决这个问题的方法。由于我刚开始在Python上使用函数注释,所以我不熟悉它。我在下面有一个问题。

当你制作一个装饰器并想在其上添加注释时,你该怎么做?

例如,如下代码。

def decorator(func: Callable[[*args,**kwargs], <what type should be here?>]) -> <??>:
    def new_func(*args, **kwargs):
        return func(*args, **kwargs)
    return new_func
Run Code Online (Sandbox Code Playgroud)

Nik*_*nes 18

请注意,PEP 612(在 Python 3.10 中实现)引入了ParamSpec,它解决了您的问题,如下所示:

from typing import Callable, TypeVar, ParamSpec

T = TypeVar('T')
P = ParamSpec('P')

def decorator(func: Callable[P, T]) -> Callable[P, T]:
    def new_func(*args: P.args, **kwargs: P.kwargs) -> T:
        return func(*args, **kwargs)
    return new_func
Run Code Online (Sandbox Code Playgroud)


A. *_*dry 4

更新

我实际上认为@Nikola Benes正确的答案不是我,即:

PEP 612引入,它提供了定义可调用参数之间的依赖关系的ParamSpec能力。

以下是您以前可以尝试ParamSpec的一种方法,但这ParamSpec是可行的方法。

对于使用 Python <3.10 的用户,您应该能够ParamSpectyping_extensions

from typing_extensions import ParamSpec
Run Code Online (Sandbox Code Playgroud)

但我还没有尝试过。它还可能取决于您的静态类型检查器(例如mypypyright等)以及该检查器的版本是否已实现对其的支持。

PyCon 2022 Typing Summit视频录制的第一部分正在ParamSpec实际演示。


旧的解决方法:

用于Any返回类型并返回另一个Callable返回类型Any。从PEP 484python 标准库开始,第一个参数Callable必须是可调用参数的类型,而不是参数本身。因此,您对*argsand **kwargsin的使用Callable是不可接受的。相反,您必须使用省略号...(它允许任意数量的位置和关键字参数类型)。

使用泛型类型 ( ) 可以更清晰地表达装饰器函数typing.TypeVar。通俗地说,泛型就是允许类型作为参数的东西。

摘自mypy 文档仅供参考mypy是一个静态类型检查器包python):

装饰器函数可以使用泛型类型来表达。泛型可以限制为使用带有关键字参数的特定类型的子类型的值bound=...。上限可用于保留装饰器装饰的包装器函数的签名。

因此,你的例子变成这样:

from typing import Any, Callable, TypeVar, cast

F = TypeVar('F', bound=Callable[..., Any])

def decorator(func: F) -> F:
    def new_func(*args, **kwargs):
        return func(*args, **kwargs)
    return cast(F, new_func)
Run Code Online (Sandbox Code Playgroud)

还解释了mypy 文档PEP 484

使用绑定 onF以便在非函数上调用装饰器将被拒绝。此外,包装函数 ( new_func) 没有进行类型检查,因为(当前)不支持使用可变数量的特定类型参数来指定回调签名,因此我们必须在最后强制转换类型。