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

Discussão em 'Python' iniciado por Stack, Julho 10, 2025.

  1. Stack

    Stack Membro Participativo

    I was reading PEP-612 and it makes typing a decorator fairly tractable. Also, the example provided in the PEP makes it look pretty easy. This example is copied directly from the 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


    However, I found it non-trivial to properly type annotate a parametrizable decorator. Check the following 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


    I'm struggling with figuring out the return type of the tag function. Also, I'm not 100% confident with the correctness of the typing of outer and inner nested functions. How do I type this properly?

    P.S. I know, as of today, mypy 0.902 doesn't support this feature fully yet.

    Continue reading...

Compartilhe esta Página