Typing functions and callbacks

typing · Essentials · 9 min

Callbacks, key functions and handlers are everywhere, and their signatures are easy to get wrong.

Callable[[int, str], bool] describes any callable taking an int and a str positionally and returning a bool. Import it from collections.abc (the typing alias is deprecated). Functions, lambdas, bound methods and objects with __call__ all match if their signature fits.

def retry(fn: Callable[[], T], attempts: int) -> T: ...
def on_event(handler: Callable[[str], None]) -> None: ...

Useful forms:

  • Callable[[], None]: takes nothing, returns nothing useful.
  • Callable[..., int]: any arguments, returns int (the ... gives up on checking arguments).
  • Callable[[str], object]: when you ignore the callback's result, object accepts functions returning anything, which is clearer than None.

Callable can't express keyword arguments, defaults or overloads. For those, define a callback Protocol with a __call__ method, which you'll meet in the Protocols lesson.

Higher-order functions that preserve the signature of what they wrap (decorators) need ParamSpec, an advanced lesson.

Example

from collections.abc import Callable

def apply_twice(fn: Callable[[int], int], x: int) -> int:
    return fn(fn(x))

def notify(handler: Callable[[str], object], msg: str) -> None:
    handler(msg)                         # result ignored, so return type object

def make_multiplier(k: int) -> Callable[[int], int]:
    return lambda n: n * k

print(apply_twice(lambda n: n + 3, 1))   # 7
print(apply_twice(make_multiplier(10), 2))   # 200
notify(print, "saved")                   # saved
notify(len, "ok")                        # fine: len returns int, object accepts it
# apply_twice(str.upper, 1)  -> checker error: incompatible Callable type

Remember

  • Callable[[Arg1, Arg2], Return] types callbacks
  • Import Callable from collections.abc
  • Use object as the return type for callbacks whose result you ignore

Watch out for

  • Writing Callable[int, int] instead of Callable[[int], int]; the parameter types must be in a list.

Check yourself

  1. Which annotation matches def f(a: int, b: int) -> str?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/typing.html#annotating-callable-objects