How to write a decorator that accepts any function and passes mypy --disallow-any-decorated?

Viewed 450

My last attempt to write a decorator that accepts any possible python function and passes mypy check with --disallow-any-decorated flag looked like this:

from typing import Any, Callable, TypeVar


T = TypeVar('T')


def decorator(func: Callable[..., T]) -> Callable[..., T]:
    def decorated(*args: Any, **kwargs: Any) -> Any:
        print('decorated')
        return func(*args, **kwargs)

    return decorated


@decorator
def foo() -> int:
    print('foo')
    return 42


print(foo())

However it still fails with Type of decorated function contains type "Any" ("Callable[..., int]")

What am I doing wrong? I also tried to use VarArg and KwArg from mypy_extensions instead of ..., but it didn't help.

2 Answers

--disallow-any-decorated:

Disallows functions that have Any in their signature after decorator transformation.

... in Callable[..., T] is a way to say that the callable takes zero or more Any arguments. So even if the declaration doesn't contain Any itself, it still resolves to a declaration containing Any.

Use the TypeVar to swallow up the whole Callable, not just the return type. Then you're clarifying to MyPy that the type of the decorated function is the same as the original, freeing it from the need to worry "Oh, but the return type here could be almost anything."

It may be necessary to use a cast on the final return line.

FuncT = TypeVar(FuncT, bound=Callable)

def decorator(func: FuncT) -> FuncT:
   ...
   return cast(FuncT, decorated)
Related