首页 > 解决方案 > 通过 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函数的返回类型。另外,我对函数类型outerinner嵌套函数的正确性不是 100% 有信心。如何正确输入?

PS 我知道,截至今天,mypy 0.902 还不完全支持此功能。

标签: pythontypestype-hintingmypypep

解决方案


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

因为您直接awaitingfunc的结果,与 PEP 的示例不同,PEP 的示例await编辑了一个单独的记录器,然后f正常调用,您outer需要使用 aCallable[[P], Awaitable[R]]而不是Callable[[P], R].

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

要注意的第三件事是,您可能希望Awaitable[T]在所有示例中都使用它(这就是我自己在上面一直使用它的原因)而不是Coroutine[T]. 这是因为 a)Coroutine采用三个类型参数而不是一个(因此您必须使用Coroutine[Any, Any, T]而不是Coroutine[T],其中前两个类型参数用于send)并且 b)Coroutine是添加了ing支持的子类型,您不需要无论如何都不要使用。Awaitablesend


推荐阅读