Python how to type hint a Callable with __wrapped__

Question:

When passing around functions, I normally type hint them with typing.Callable.

The docs for collections.abc.Callable state that it has four dunder methods:

class collections.abc.Callable

ABCs for classes that provide respectively the methods __contains__(), __hash__(), __len__(), and __call__().

At one point, I want to check if there is a __wrapped__ attribute on a function. This works fine at runtime via a check with hasattr(func, "__wrapped__").

When static type checking with mypy, it reports: error: "Callable[..., Any]" has no attribute "__wrapped__" [attr-defined]. This makes sense to me, as Callable isn’t supposed to have a __wrapped__ attribute.

How can I properly type hint a Callable with a __wrapped__ attribute? Is there some other type hint or workaround I can do?


Code Sample

I am using mypy==0.782 and Python==3.8.2:

from functools import wraps
from typing import Callable


def print_int_arg(arg: int) -> None:
    """Print the integer argument."""
    print(arg)


@wraps(print_int_arg)
def wrap_print_int_arg(arg: int) -> None:
    print_int_arg(arg)
    # do other stuff


def print_is_wrapped(func: Callable) -> None:
    """Print if a function is wrapped."""
    if hasattr(func, "__wrapped__"):
        # error: "Callable[..., Any]" has no attribute "__wrapped__"  [attr-defined]
        print(f"func named {func.__name__} wraps {func.__wrapped__.__name__}.")


print_is_wrapped(wrap_print_int_arg)

Answers:

Obviously the easy answer is to add a # type: ignore comment. However, this isn’t actually solving the problem, IMO.

I decided to make a type stub for a callable with a __wrapped__ attribute. Based on this answer, here is my current solution:

from typing import Callable, cast


class WrapsCallable:
    """Stub for a Callable with a __wrapped__ attribute."""

    __wrapped__: Callable

    __name__: str

    def __call__(self, *args, **kwargs):
        ...


def print_is_wrapped(func: Callable) -> None:
    """Print if a function is wrapped."""
    if hasattr(func, "__wrapped__"):
        func = cast(WrapsCallable, func)
        print(f"func named {func.__name__} wraps {func.__wrapped__.__name__}.")

And mypy now reports Success: no issues found in 1 source file.

I feel as if this is a lot of boiler-plate code, and would love a more streamlined answer.

Mypy complain on use of __wrapped__ in print statement.
The following trick makes mypy happy

def print_is_wrapped(func: Callable) -> None:
    """Print if a function is wrapped."""
    if hasattr(func, "__wrapped__"):
        wrapped_name = getattr(func, "__wrapped__").__name__
        print(f"func named {func.__name__} wraps {wrapped_name}.")
Answered By: Serg Suslenkov