[Python] Typing a callable with ParamSpec or no args (`Callable[P, Any] | Callable[[], Any]`)

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

  1. Stack

    Stack Membro Participativo

    Here's a decorator that accepts a callable (fn: Callable[P, Any]) with the same signature as the function getting wrapped. It works and type checks.

    import inspect
    from typing import Any, Callable, ParamSpec, TypeVar, Union

    P = ParamSpec("P")
    T = TypeVar("T")


    def deco1(fn: Callable[P, Any]) -> Callable[[Callable[P, T]], Callable[P, T]]:
    def decorator(wrapped_fn: Callable[P, T]) -> Callable[P, T]:
    def inner(*args: P.args, **kwargs: P.kwargs) -> T:
    fn(*args, **kwargs)
    return wrapped_fn(*args, **kwargs)

    return inner

    return decorator


    def logger1(x: int, y: str) -> None:
    print(f"logger1: x={x}, y={y}")


    @deco1(logger1)
    def wrapped1(x: int, y: str) -> None: ...


    wrapped1(1, "test") # prints "logger1: x=1, y=test"


    Here's a decorator that accepts a callable (fn: Callable[[], Any]) with no arguments. It also works and type checks.

    def deco2(fn: Callable[[], Any]) -> Callable[[Callable[P, T]], Callable[P, T]]:
    def decorator(wrapped_fn: Callable[P, T]) -> Callable[P, T]:
    def inner(*args: P.args, **kwargs: P.kwargs) -> T:
    fn()
    return wrapped_fn(*args, **kwargs)

    return inner

    return decorator


    def logger2() -> None:
    print(f"logger2")


    @deco2(logger2)
    def wrapped2(x: int, y: str) -> None: ...


    wrapped2(1, "test") # prints "logger2"


    I'd like to combine deco1 and deco2 into a single function deco3. The following works at run-time, but I can't get the type checker to pass. Is this possible?

    def deco3(
    fn: Callable[[], Any] | Callable[P, Any],
    ) -> Callable[[Callable[P, T]], Callable[P, T]]:
    def decorator(wrapped_fn: Callable[P, T]) -> Callable[P, T]:
    def inner(*args: P.args, **kwargs: P.kwargs) -> T:
    if len(inspect.signature(fn).parameters):
    fn(*args, **kwargs)
    else:
    fn() # pyright error: Arguments for ParamSpec "P@deco3" are missing (reportCallIssue)
    return wrapped_fn(*args, **kwargs)

    return inner

    return decorator


    @deco3(logger1)
    def wrapped_with_logger1(x: int, y: str) -> None: ...


    @deco3(logger2) # pyright error: Argument of type "(x: int, y: str) -> None" cannot be assigned to parameter of type "() -> T@deco3"
    def wrapped_with_logger2(x: int, y: str) -> None: ...


    wrapped_with_logger1(1, "test") # prints "logger1: x=1, y=test"
    wrapped_with_logger2(1, "test") # prints "logger2" but pyright error: Expected 0 positional arguments (reportCallIssue)

    Continue reading...

Compartilhe esta Página