How to combine a custom protocol with the Callable protocol?
Since typing.Callable
corresponds to collections.abc.Callable
, you can just define a Protocol
that implements __call__
:
class CallableWithAttrs(Protocol):
attr1: str
attr2: str
def __call__(self, *args, **kwargs): pass
One can parameterise a Protocol
by a Callable
:
from typing import Callable, TypeVar, Protocol
C = TypeVar('C', bound=Callable) # placeholder for any Callable
class CallableObj(Protocol[C]): # Protocol is parameterised by Callable C ...
attr1: str
attr2: str
__call__: C # ... which defines the signature of the protocol
This creates an intersection of the Protocol
itself with an arbitrary Callable
.
A function that takes any callable C
can thus return CallableObj[C]
, a callable of the same signature with the desired attributes:
def decorator(func: C) -> CallableObj[C]: ...
MyPy properly recognizes both the signature and attributes:
def dummy(arg: str) -> int: ...
reveal_type(decorator(dummy)) # CallableObj[def (arg: builtins.str) -> builtins.int]'
reveal_type(decorator(dummy)('Hello')) # int
reveal_type(decorator(dummy).attr1) # str
decorator(dummy)(b'Fail') # error: Argument 1 to "dummy" has incompatible type "bytes"; expected "str"
decorator(dummy).attr3 # error: "CallableObj[Callable[[str], int]]" has no attribute "attr3"; maybe "attr2"?