If I define a passed in function using the Callable syntax, mypy complains if I then call a keyword argument on that function. This is makes writing things like decorators problematic
Sample code:
from typing import Callable
def test_func(a, b, c=0, d=0):
# type: (int, int, int, int) -> int
return a + b + c + d
def check_func(method):
# type: (Callable[[int, int, int], int]) -> int
return method(1, 2, d=5)
print(check_func(test_func))
$ mypy sample_python.py
sample_python.py: note: In function "check_func":
sample_python.py:9: error: Unexpected keyword argument "d"
As an aside here, I the following function code also raises mypy errors:
from typing import Callable
def test_func(a, b, *, c, d):
# type: (int, int, int, int) -> int
return a + b + c + d
def check_func(method):
# type: (Callable[[int, int, int, int], int]) -> int
return method(1, 2, c=5, d=10)
print(check_func(test_func))
You can use Callable[..., int] here, with literally three dots.
--Guido (mobile)
Is there any way to be more specific?
Sorry, there's nothing more specific; it's hard to fit this in Python's syntactic constraints on the X[Y] notation. For decorators that don't change the signature of the thing they're decorating, you can use this:
F = TypeVar('F', bound=Callable[..., Any])
def decorator(func: F) -> F:
def wrapper(*args, **kwds):
return func(*args, **kwds)
return cast(F, wrapper)
But obviously this doesn't work if the decorator changes the signature, nor if the wrapper needs to pass a keyword arg.
I hit this error when I was trying to constrain the types of injected dependencies, so my solution may not apply to your decorator problem. But, this thread comes up first on google for this error so I'm just adding this as a potential solution others might find helpful. I just replaced my anonymous type with an interface by doing the moral equivalent to the following transformation of your code example.
from abc import ABC, abstractmethod
class FourNumGizmo(ABC):
@abstractmethod
def __call__(
self,
a: int,
b: int,
c: int = 0,
d: int = 0
) -> int:
pass
class MyAdder(FourNumGizmo):
def __call__(
self,
a: int,
b: int,
c: int = 0,
d: int = 0
) -> int:
return a + b + c + d
def check_four_num_gizmos(gizmo: FourNumGizmo) -> int:
return gizmo(1, 2, d=5)
print(check_four_num_gizmos(MyAdder()))
Might be worth changing the error message to something that explains that Callback does not support keyword arguments, and a link to this bug for alternatives.
I think we should suggest using Protocols like we do here https://mypy.readthedocs.io/en/latest/protocols.html#callback-protocols. Perhaps that section could be made more generic? Then we can link to that if someone tries to use keywords on a Callable.
Most helpful comment
Sorry, there's nothing more specific; it's hard to fit this in Python's syntactic constraints on the X[Y] notation. For decorators that don't change the signature of the thing they're decorating, you can use this:
But obviously this doesn't work if the decorator changes the signature, nor if the wrapper needs to pass a keyword arg.