Lists and dictionaries of functions leverage Python’s first-class function objects — allowing you to store functions in lists, dictionaries, or other structures for dynamic dispatch, strategy patterns, plugin systems, callbacks, batch processing, or configurable workflows. This turns functions into data: select which one to call based on keys, indices, conditions, or user input, making code highly flexible, extensible, and DRY. In 2026, this pattern is everywhere — dynamic operation selection in data pipelines, ML model ensembles, API handlers, GUI button actions, pandas/Polars custom transformations, and plugin architectures. It eliminates long if-elif chains and enables runtime behavior changes without rewriting code.
Here’s a complete, practical guide to lists and dictionaries of functions in Python: basic storage and invocation, dynamic dispatch patterns, real-world use cases (strategies, callbacks, batch ops), and modern best practices with type hints, error handling, performance, and pandas/Polars integration.
Functions in lists — call by index, apply same input to multiple functions, or batch execution.
def add(a: int, b: int) -> int: return a + b
def subtract(a: int, b: int) -> int: return a - b
def multiply(a: int, b: int) -> int: return a * b
def divide(a: int, b: int) -> float: return a / b if b != 0 else float('inf')
math_ops = [add, subtract, multiply, divide]
# Call the third function (multiply) with 4 and 2
result = math_ops[2](4, 2)
print(result) # 8
# Apply all functions to same inputs
x, y = 10, 5
results = [op(x, y) for op in math_ops]
print(results) # [15, 5, 50, 2.0]
Functions in dictionaries — key-based dispatch, strategy pattern, configurable behavior, plugin-like systems.
operations = {
"add": add,
"subtract": subtract,
"multiply": multiply,
"divide": divide
}
def compute(x: int, y: int, op_name: str) -> float:
func = operations.get(op_name)
if func is None:
raise ValueError(f"Unknown operation: {op_name}")
return func(x, y)
print(compute(10, 5, "add")) # 15
print(compute(10, 5, "multiply")) # 50
# compute(10, 5, "power") ? raises ValueError
Real-world pattern: dynamic transformations in pandas/Polars — store functions in dict, apply based on config or column type.
import pandas as pd
def normalize(col): return (col - col.mean()) / col.std()
def log_transform(col): return np.log1p(col)
def square(col): return col ** 2
transformations = {
"numeric": normalize,
"skewed": log_transform,
"count": square
}
df = pd.DataFrame({
'sales': [100, 200, 300],
'visits': [10, 20, 30],
'clicks': [5, 15, 25]
})
# Apply different functions per column
for col in df.columns:
col_type = "numeric" if col == "sales" else "count"
df[f"{col}_transformed"] = transformations[col_type](df[col])
print(df)
Best practices make lists/dicts of functions safe, readable, and performant. Use type hints — Callable[[int, int], float] or dict[str, Callable[[int, int], float]] — improves clarity and mypy checks. Prefer dicts over lists for named dispatch — keys are self-documenting ("add", "normalize") vs magic indices. Modern tip: use Polars expressions or pandas method chaining — DRY pipelines with function dispatch. Validate keys — if op_name not in operations: raise ValueError. Use functools.partial — pre-bind arguments for reusable partial functions. Use decorators — wrap functions for logging, timing, validation before storing. Store in modules — import as from transformations import operations for reuse across files. Handle missing keys gracefully — default function or raise informative error. Test dispatch — assert correct function called for each key. Combine with abc.ABC or protocols — define function signatures/interfaces for type safety. Use __call__ in classes — make objects callable like functions for advanced strategies.
Lists and dictionaries of functions turn behavior into data — dynamic dispatch, strategies, plugins, batch ops, configurable pipelines. In 2026, use type hints, prefer dicts for named access, partials/decorators for customization, validate keys, and integrate with Polars/pandas for scalable transformations. Master this pattern, and you’ll write flexible, extensible, testable Python code that adapts without rewriting logic.
Next time you have multiple similar operations — store functions in a list or dict. It’s Python’s cleanest way to say: “Call the right function without if-elif chains.”