For example, mypy accepts this:
class P(Protocol):
def __call__(self, a: int)->int: ...
def call(a: int, f: P)->int:
return f(a)
def add_one(a: int)->int:
return a+1
call(1,add_one)
but not this:
class P(Protocol):
def __call__(self, a: int, **kwargs: Any)->int: ...
def call(a: int, f: P)->int:
return f(a)
def add_one(a: int)->int:
return a+1
def add_one_and_b(a: int, b: int= 1)->int:
return a+1+b
call(1,add_one)
call(1,add_one_and_b)
errors (same error for the last two lines):
error: Argument 2 to "call" has incompatible type "Callable[[int], int]"; expected "P" [arg-type]
How can one specify the type "method that takes as argument an int, possibly keyword arguments; and returns an int" ? (with or without using Protocol)
Short answer: Callable[[int], int]
is all you need.
Protocols specify things an object definitely can do. Not things it can't, or things it might be able to do.
You're trying to specify "takes as argument an int, possibly keyword arguments; and returns an int". The only thing you definitely can do with a callable like that is call it with an int, and get an int. If you want to use a protocol, that's what you should specify with your protocol:
class P(Protocol):
def __call__(self, a: int, /)->int: ...
This is almost identical to your original protocol. The only thing I've changed is adding a /
to mark all arguments before it positional-only. That doesn't mean your objects need to take a positional-only argument; it means that the protocol requires objects to support passing the argument positionally, but doesn't require objects to support passing it by any particular keyword name.
However, you don't need a custom protocol for this. Callable
can specify this directly:
P = Callable[[int], int]
If you test this on the mypy playground:
from typing import Callable, Protocol
class P1(Protocol):
def __call__(self, a: int, /) -> int: ...
P2 = Callable[[int], int]
def call1(a: int, f: P1) -> int:
return f(a)
def call2(a: int, f: P2) -> int:
return f(a)
def add_one(a: int) -> int:
return a+1
def add_one_and_b(a: int, b: int= 1) -> int:
return a+1+b
def add_one_with_different_arg_name(b: int) -> int:
return b+1
call1(1,add_one)
call1(1,add_one_and_b)
call1(1, add_one_with_different_arg_name)
call2(1,add_one)
call2(1,add_one_and_b)
call2(1, add_one_with_different_arg_name)
you'll find that mypy reports no errors for any of the calls.