I have a function
def run_thing(cb: Callback) -> Result:
w: Widget = make_widget()
res: Result = cb(w)
return res
I can imagine two ways to define the Callback type.
# Option 1: Callable
# https://docs.python.org/3/library/typing.html#annotating-callables
Callback = typing.Callable[[Widget],Result]
# Option 2: Protocol
# https://docs.python.org/3/library/typing.html#typing.Protocol
class Callback(typing.Protocol):
def __call__(self,w: Widget) -> Result:
...
What are the nuances to consider in the decision between the two above type definitions?
Callable
can be used for simple cases, but will be too limited for complex signatures.
This is explained by the documentation that also suggests to define a Protocol
as an alternative with the following quote and example snippet :
Callable cannot express complex signatures such as functions that take a variadic number of arguments, overloaded functions, or functions that have keyword-only parameters. However, these signatures can be expressed by defining a
Protocol
class with a__call__()
method:
from collections.abc import Iterable
from typing import Protocol
class Combiner(Protocol):
def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ...
def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
for item in data:
...
def good_cb(*vals: bytes, maxlen: int | None = None) -> list[bytes]:
...
def bad_cb(*vals: bytes, maxitems: int | None) -> list[bytes]:
...
batch_proc([], good_cb) # OK
batch_proc([], bad_cb) # Error! Argument 2 has incompatible type because of
# different name and kind in the callback