通过 ParamSpec 输入一个接受参数的装饰器(PEP-612,Python 3.10)

Typing a Decorator that Accepts Arguments via ParamSpec (PEP-612, Python 3.10)

我正在阅读 PEP-612,它使输入装饰器变得相当容易。此外,PEP 中提供的示例使其看起来非常简单。本例直接从 PEP 复制而来:

from typing import ParamSpec, TypeVar
from collections.abc import Callable, Awaitable

P = ParamSpec("P")
R = TypeVar("R")

def add_logging(f: Callable[P, R]) -> Callable[P, Awaitable[R]]:
  async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
    await log_to_database()
    return f(*args, **kwargs)
  return inner

@add_logging
def takes_int_str(x: int, y: str) -> int:
  return x + 7

await takes_int_str(1, "A") # Accepted
await takes_int_str("B", 2) # Correctly rejected by the type checker

但是,我发现正确键入注释可参数化装饰器并非易事。检查以下 MRE:

import functools
import inspect 
from collections.abc import Callable, Coroutine
from typing import ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")

def tag(*names: str) -> ??:
    """This decorator just tags an async function with the provided name(s)."""

    for name in names:
        if not isinstance(name, str):
            raise TypeError("tag name must be a string")

    def outer(func: Callable[P, R]) -> Callable[P, Coroutine[R]]:
        func.tags = names
        if not inspect.iscoroutinefunction(func):
            raise TypeError("tagged function must be an async function")

        @functools.wraps(func)
        async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
            result = await func(*args, **kwargs)
            return result

        return inner

    return outer

我正在努力弄清楚 tag 函数的 return 类型。另外,我对 outerinner 嵌套函数的输入正确性不是 100% 有信心。我该如何正确输入?

P.S。我知道,到今天为止,mypy 0.902 还没有完全支持这个功能。

首先要注意的是,您的参数化装饰器示例不仅仅是 PEP 的装饰器示例加上参数化。相反,您的第二个示例的装饰器(在参数化之后)采用异步函数,而 PEP 的示例采用同步函数。

因为你是直接 awaiting func 的结果,不像 PEP 的例子awaited 一个单独的记录器然后正常调用 f,你的outer 需要取 Callable[[P], Awaitable[R]] 而不是 Callable[[P], R].

第二个需要注意的是,关于tags return类型,你可以通过添加reveal_type(outer)来计算它,这将是return类型tag。我没有 运行 这个(因为 mypy 还没有真正支持你的例子),但它应该说 Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]。换句话说,tag return 是一个本身带有异步函数的装饰器,return 是一个异步函数。

第三点要注意的是,您可能希望在所有示例中使用 Awaitable[T](这就是为什么我自己在上面一直使用它的原因)而不是 Coroutine[T]。这是因为 a) Coroutine 采用三个类型参数而不是一个(所以你必须使用 Coroutine[Any, Any, T] 而不是 Coroutine[T],其中前两个类型参数用于 send) and b) Coroutine is a subtypeAwaitable 增加了对 sending 的支持,但您无论如何都不会使用它。