Higher-order functions like sorting with a custom key, event callbacks, and plugin systems all need to accept functions as arguments. Callable types let you specify exactly what kind of function is expected - what arguments it takes and what it returns.

Callable Objects

Objects with __call__ are also callable:

callable_basics.py
# Callable basics

from typing import Callable

Op = Callable[[int, int], int]

# Functions match Callable types

def add(a: int, b: int) -> int:
    return a + b


def mul(a: int, b: int) -> int:
    return a * b

op: Op = add
print("add:", op(2, 3))

op = mul
print("mul:", op(2, 3))

# Lambda also matches
op = lambda a, b: a - b
print("sub:", op(7, 2))

key_functions.py
# Callable key functions

from typing import Callable

KeyFn = Callable[[str], int]

# A custom sort using a key function

def sort_strings(items: list[str], key: KeyFn) -> list[str]:
    return sorted(items, key=key)

items = 

print("by length:", sort_strings(items, key=len))
print("by last char:", sort_strings(items, key=lambda s: ord(s[-1])))

# Callable key functions

from typing import Callable

KeyFn = Callable[[str], int]

# A custom sort using a key function

def sort_strings(items: list[str], key: KeyFn) -> list[str]:
    return sorted(items, key=key)

items = 

print("by length:", sort_strings(items, key=len))
print("by last char:", sort_strings(items, key=lambda s: ord(s[-1])))

# Callable key functions

from typing import Callable

KeyFn = Callable[[str], int]

# A custom sort using a key function

def sort_strings(items: list[str], key: KeyFn) -> list[str]:
    return sorted(items, key=key)

items = 

print("by length:", sort_strings(items, key=len))
print("by last char:", sort_strings(items, key=lambda s: ord(s[-1])))

callbacks.py
# Callbacks

from typing import Callable

OnEvent = Callable[[str], None]

# Register/trigger

def trigger(event: str, handler: OnEvent) -> None:
    handler(event)


def print_handler(event: str) -> None:
    print("handled:", event)

trigger("login", print_handler)
trigger("logout", lambda e: print("lambda handled:", e))

higher_order.py
# Higher-order functions

from typing import Callable

UnaryInt = Callable[[int], int]

# Factory

def make_multiplier(factor: int) -> UnaryInt:
    def mul(x: int) -> int:
        return x * factor

    return mul

by2 = make_multiplier(2)
by5 = make_multiplier(5)

print("by2(10) =", by2(10))
print("by5(10) =", by5(10))

callable_objects.py
# Callable objects

from typing import Callable

# A callable class
class Adder:
    def __init__(self, n: int) -> None:
        self.n = n

    def __call__(self, x: int) -> int:
        return x + self.n

add10 = Adder(10)
print("add10(5) =", add10(5))

# It can be treated like a Callable[[int], int]
Fn = Callable[[int], int]
f: Fn = add10
print("f(7) =", f(7))

When to Use Callable

  • Callbacks / event handlers
  • Customizing behavior (strategy pattern)
  • Mapping / filtering utilities
  • Dependency injection in small scripts If you need many overloads, consider Protocol instead (beyond this page).
Callable - a type annotation `Callable[[ArgTypes...], ReturnType]` describing a function's signature
higher-order function - a function that takes another function as an argument or returns a function

Exercise: practical.py

Create a validator registry using Callable types