First-Class Functions, Callables, and Lambdas

Updated

September 7, 2026

First-Class Functions, Callables, and Lambdas

After reading this chapter, you will treat functions as first-class citizens, build command dispatch tables, author concise lambda expressions, and inspect callables.

Mental model

In Python, functions are first-class objects. This means a function is just an object like any other integer or string: 1. It can be assigned to a variable name. 2. It can be stored in a collection (list, dict, set). 3. It can be passed as an argument to another function. 4. It can be returned as the result of another function.

       ┌───────────────────────────────┐
       │ PyFunctionObject: calculate() │
       └──────────────┬────────────────┘
                      │ stored in
       ┌──────────────▼────────────────┐
       │ command_table = {             │
       │   "calc": calculate,          │
       │   "ping": health_check,       │
       │ }                             │
       └──────────────┬────────────────┘
                      │ looked up & called
       command_table["calc"](args)

An anonymous function (lambda) is an inline function defined without a def statement, limited to a single expression.


Minimal example

Save as first_class_demo.py:

# first_class_demo.py
from collections.abc import Callable

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

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

def execute_operation(op: Callable[[int, int], int], x: int, y: int) -> int:
    """Higher-order function accepting a function as an argument."""
    return op(x, y)

def main() -> None:
    # 1. Pass function by name
    res1 = execute_operation(add, 10, 5)
    res2 = execute_operation(multiply, 10, 5)

    # 2. Pass anonymous lambda
    res3 = execute_operation(lambda a, b: a - b, 10, 5)

    print(f"Add      : {res1}")
    print(f"Multiply : {res2}")
    print(f"Lambda   : {res3}")

if __name__ == "__main__":
    main()

Run via uv run python first_class_demo.py:

Add      : 15
Multiply : 50
Lambda   : 5

Worked examples

Case 1: Command Dispatch Tables (Replacing if/elif ladders)

Instead of a long, brittle ladder of if/elif statements, use a dictionary of functions:

# dispatch_table.py
from collections.abc import Callable

def handle_start(target: str) -> str:
    return f"Started service: {target}"

def handle_stop(target: str) -> str:
    return f"Stopped service: {target}"

def handle_restart(target: str) -> str:
    return f"Restarted service: {target}"

DISPATCH_REGISTRY: dict[str, Callable[[str], str]] = {
    "start": handle_start,
    "stop": handle_stop,
    "restart": handle_restart,
}

def dispatch_command(cmd: str, target: str) -> str:
    handler = DISPATCH_REGISTRY.get(cmd)
    if not handler:
        raise ValueError(f"Unknown command: {cmd!r}. Available: {list(DISPATCH_REGISTRY.keys())}")
    return handler(target)

if __name__ == "__main__":
    print(dispatch_command("start", "nginx"))
    print(dispatch_command("restart", "postgres"))

Run:

uv run python dispatch_table.py

Case 2: Custom sorting keys with lambdas and operator

When sorting complex structured data, pass a key function to sorted():

# sorting_keys.py
import operator

def main() -> None:
    containers = [
        {"name": "worker-1", "cpu_percent": 84.5, "memory_mb": 512},
        {"name": "worker-2", "cpu_percent": 22.1, "memory_mb": 1024},
        {"name": "worker-3", "cpu_percent": 91.0, "memory_mb": 256},
    ]

    # Sort by CPU descending using a lambda
    by_cpu = sorted(containers, key=lambda c: c["cpu_percent"], reverse=True)
    print("Sorted by CPU (descending):")
    for c in by_cpu:
        print(f"  {c['name']:10s} -> {c['cpu_percent']}%")

    # Sort by memory using operator.itemgetter (faster in C)
    by_mem = sorted(containers, key=operator.itemgetter("memory_mb"))
    print("\nSorted by memory (ascending):")
    for c in by_mem:
        print(f"  {c['name']:10s} -> {c['memory_mb']}MB")

if __name__ == "__main__":
    main()

Run:

uv run python sorting_keys.py

Pitfalls

Pitfall 1: Over-complicating lambdas

Lambdas should be simple one-liners (typically for sorted(key=...)). If your lambda needs complex logic, loops, or multiple statements, write a standard named def function.

Pitfall 2: Assigning a lambda to a variable instead of def

# Bad style (violates PEP 8):
my_func = lambda x: x * 2

# Good style:
def my_func(x):
    return x * 2

Exercises

  1. Build a text pipeline function process_text(text: str, transforms: list[Callable[[str], str]]) -> str that applies a list of transformation functions sequentially.
  2. Given a list of tuples [(1, "b"), (3, "a"), (2, "c")], sort them alphabetically by the second element using a lambda key.
  3. Use the built-in callable() function to verify which standard objects in a mixed list can be called as functions.

Further reading

  • Python Documentation: Functional Programming HOWTO.
  • Python Standard Library: operator module (itemgetter, attrgetter).
  • PEP 8: Programming Recommendations regarding lambda assignment.