How do I define a Python protocol for a type that is:
This is my attempt:
from typing import Any, Protocol, TypeVar
T = TypeVar("T", covariant=True)
class Operation(Protocol[T]):
def __call__(self, **kwargs: Any) -> T:
pass
# some example functions that should be a structural sub-type of "Operation[str]"
def sumint(*, x: Any, y: Any) -> str:
return f"{x} + {y} = {x + y}"
def greet(*, name: Any = "World") -> str:
return f"Hello {name}"
# an example function that takes an "Operation[str]" as an argument
def apply_operation(operation: Operation[str], **kwargs: Any) -> str:
return operation(**kwargs)
if __name__ == "__main__":
print(apply_operation(sumint, x=2, y=2))
# prints: 2 + 2 = 4
print(apply_operation(greet, name="Stack"))
# prints: Hello Stack
However, mypy produces the error:
example.py:26: error: Argument 1 to "apply_operation" has incompatible type "Callable[[NamedArg(Any, 'x'), NamedArg(Any, 'y')], str]"; expected "Operation[str]"
example.py:28: error: Argument 1 to "apply_operation" has incompatible type "Callable[[DefaultNamedArg(Any, 'name')], str]"; expected "Operation[str]"
Found 2 errors in 1 file (checked 1 source file)
What am I doing wrong? How do I make mypy happy?
I can't answer your question about exactly why MyPy isn't happy — but here's a different approach that MyPy does seem to be happy with:
from typing import Any, Callable, TypeVar
T = TypeVar("T", covariant=True)
Operation = Callable[..., T]
# some example functions that should be a structural sub-type of "Operation[str]"
def sumint(*, x: int = 1, y: int = 2) -> str:
return f"{x} + {y} = {x + y}"
def greet(*, name: str = "World") -> str:
return f"Hello {name}"
# an example function that takes an "Operation[str]" as an argument
def apply_operation(operation: Operation[str], **kwargs: Any) -> str:
return operation(**kwargs)
if __name__ == "__main__":
print(apply_operation(sumint, x=2, y=2))
# prints: 2 + 2 = 4
print(apply_operation(greet, name="Stack"))
# prints: Hello Stack