Python 3 type hinting for decorator Python 3 type hinting for decorator python-3.x python-3.x

Python 3 type hinting for decorator


You can't use Callable to say anything about additional arguments; they are not generic. Your only option is to say that your decorator takes a Callable and that a different Callable is returned.

In your case you can nail down the return type with a typevar:

RT = TypeVar('RT')  # return typedef inject_user() -> Callable[[Callable[..., RT]], Callable[..., RT]]:    def decorator(func: Callable[..., RT]) -> Callable[..., RT]:        def wrapper(*args, **kwargs) -> RT:            # ...

Even then the resulting decorated foo() function has a typing signature of def (*Any, **Any) -> builtins.bool* when you use reveal_type().

Various proposals are currently being discussed to make Callable more flexible but those have not yet come to fruition. See

for some examples. The last one in that list is an umbrella ticket that includes your specific usecase, the decorator that alters the callable signature:

Mess with the return type or with arguments

For an arbitrary function you can't do this at all yet -- there isn't even a syntax. Here's me making up some syntax for it.


PEP 612 was accepted after the accepted answer, and we now have typing.ParamSpec and typing.Concatenate in Python 3.10. With these variables, we can correctly type some decorators that manipulate positional parameters.

Note that mypy's support for PEP 612 is still under way (tracking issue).

The code in question can be typed like this (though not tested on mypy for the reason above)

from typing import Callable, ParamSpec, Concatenate, TypeVarParam = ParamSpec("Param")RetType = TypeVar("RetType")OriginalFunc = Callable[Param, RetType]DecoratedFunc = Callable[Concatenate[Param, str], RetType]def get_authenticated_user(): return "John"def inject_user() -> Callable[[OriginalFunc], DecoratedFunc]:    def decorator(func: OriginalFunc) -> DecoratedFunc:        def wrapper(*args, **kwargs) -> RetType:            user = get_authenticated_user()            if user is None:                raise Exception("Don't!")            return func(*args, user, **kwargs)  # <- call signature modified        return wrapper    return decorator@inject_user()def foo(a: int, username: str) -> bool:    print(username)    return bool(a % 2)foo(2)      # Type check OKfoo("no!")  # Type check should fail


I tested this in Pyright.

from typing import Any, Callable, Type, TypeVarT = TypeVar('T')def typing_decorator(rtype: Type[T]) -> Callable[..., Callable[..., T]]:    """    Useful function to typing a previously decorated func.    ```    @typing_decorator(rtype = int)    @my_decorator()    def my_func(a, b, *c, **d):        ...    ```    In Pyright the return typing of my_func will be int.    """    def decorator(function: Any) -> Any:        def wrapper(*args: Any, **kwargs: Any) -> Any:            return function(*args, **kwargs)        return wrapper    return decorator  # type: ignore