First-Class Functions, Callables, and Lambdas
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.pyCase 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.pyPitfalls
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 * 2Exercises
- Build a text pipeline function
process_text(text: str, transforms: list[Callable[[str], str]]) -> strthat applies a list of transformation functions sequentially. - Given a list of tuples
[(1, "b"), (3, "a"), (2, "c")], sort them alphabetically by the second element using a lambda key. - 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:
operatormodule (itemgetter, attrgetter). - PEP 8: Programming Recommendations regarding lambda assignment.