I’m trying to implement and annotate a decorator for functions without keyword arguments (but with variable number of arguments) using typing
‘s Protocol
as user-defined Generic
types.
But MyPy issues me the following errors (repeated in the MRE above the lines causing it):
-
error: Argument 1 to "my_decorator" has incompatible type "Callable[[], int]"; expected "FunctionToDecorate[Never, Never]" [arg-type]
. -
error: Argument 1 to "my_decorator" has incompatible type "Callable[[int], int]"; expected "FunctionToDecorate[Never, Never]" [arg-type]
. -
error: Argument 1 to "my_decorator" has incompatible type "Callable[[int, Any], Any]"; expected "FunctionToDecorate[Never, Never]" [arg-type]
.
What is it caused by and how to solve it?
Is it caused by the way I use ParamSpec
in my Protocol
s?
Why does it involve Never
and why are the received types not consistent with the expected FunctionToDecorate[Never, Never]
?
MRE (MyPy playground here):
#!/usr/bin/env python3.10
from typing import Protocol, ParamSpec, Generic, TypeVar
T = TypeVar("T", covariant=True)
P = ParamSpec("P")
# Callable types (as Protocols):
class FunctionToDecorate(Generic[T, P], Protocol):
def __call__(self, *args: P.args) -> T: ...
class DecoratedFunction(Generic[T, P], Protocol):
def __call__(self, *args: P.args) -> T: ...
# The decorator
def my_decorator(f: FunctionToDecorate[T, P]) -> DecoratedFunction[T, P]:
def my_decorated_function(*args) -> T:
return f(*args)
return my_decorated_function
# error: Argument 1 to "my_decorator" has incompatible type "Callable[[], int]"; expected "FunctionToDecorate[Never, Never]" [arg-type]
@my_decorator
def get_val() -> int:
return 1
# error: Argument 1 to "my_decorator" has incompatible type "Callable[[int], int]"; expected "FunctionToDecorate[Never, Never]" [arg-type]
@my_decorator
def double_val(val: int) -> int:
return val * 2
# error: Argument 1 to "my_decorator" has incompatible type "Callable[[int, Any], Any]"; expected "FunctionToDecorate[Never, Never]" [arg-type]
@my_decorator
def get_val_with_kwarg(val: int, kw=None):
return val
def main():
print("get_val()`s return value:", get_val())
print("double_val()`s return value:", double_val(2))
print("get_val_with_kwarg()`s return value:", get_val_with_kwarg(3))
if __name__ == "__main__":
main()
The output:
get_val()`s return value: 1
double_val()`s return value: 4
get_val_with_kwarg()`s return value: 3
NB:
The use of Protocol
is justified by the complexity of the involved signatures Callable
can’t represent. The above MRE is simply what my problem seems to boil down to when using these tools.
NB-2:
I’m also aware of the simpler syntax introduced by Python 3.12 for generic types. The use of TypeVar
is justified by a backward compatibility constraint to Python 3.10.