functools Module — Core Concepts
What functools Is For
The functools module provides higher-order functions — functions that act on or return other functions. It’s part of Python’s standard library and handles common patterns that would otherwise require repetitive boilerplate.
The Key Tools
lru_cache — Automatic Memoization
Wraps a function so repeated calls with the same arguments return cached results:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(100) # Returns instantly (without cache: takes forever)
“LRU” means Least Recently Used — when the cache is full, the oldest unused entry gets evicted. Set maxsize=None for unlimited caching (use cautiously with memory).
Cache info is available via fibonacci.cache_info(), showing hits, misses, and current size.
cache — Simple Unbounded Cache
Added in Python 3.9, @cache is shorthand for @lru_cache(maxsize=None):
from functools import cache
@cache
def expensive_lookup(key):
# Database call, API request, heavy computation
return fetch_from_database(key)
Use when you know the number of unique inputs is bounded.
partial — Pre-Fill Arguments
Creates a new function with some arguments already set:
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
square(5) # 25
cube(3) # 27
Real-world use: configuring callback functions, setting default database connections, or creating specialized versions of generic functions.
wraps — Preserve Function Metadata
When writing decorators, @wraps copies the original function’s name, docstring, and other attributes to the wrapper:
from functools import wraps
def timer(func):
@wraps(func) # Without this, help(my_function) shows "wrapper" info
def wrapper(*args, **kwargs):
import time
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
return result
return wrapper
@timer
def my_function():
"""Does important work."""
pass
print(my_function.__name__) # "my_function" (not "wrapper")
print(my_function.__doc__) # "Does important work."
Without @wraps, debugging and documentation tools break because the decorated function loses its identity.
reduce — Cumulative Computation
Applies a function of two arguments cumulatively to a sequence:
from functools import reduce
# Sum: 1 + 2 + 3 + 4 + 5
total = reduce(lambda a, b: a + b, [1, 2, 3, 4, 5]) # 15
# Find maximum
biggest = reduce(lambda a, b: a if a > b else b, [3, 1, 4, 1, 5, 9]) # 9
For simple cases, built-in functions like sum(), max(), and min() are preferred. reduce shines for custom accumulation logic — like building a nested dictionary from a list of keys.
total_ordering — Complete Comparisons From Two Methods
Define __eq__ and one comparison method, get all six for free:
from functools import total_ordering
@total_ordering
class Version:
def __init__(self, major, minor):
self.major = major
self.minor = minor
def __eq__(self, other):
return (self.major, self.minor) == (other.major, other.minor)
def __lt__(self, other):
return (self.major, self.minor) < (other.major, other.minor)
# Now <=, >, >=, != all work automatically
Version(1, 2) <= Version(1, 3) # True
Version(2, 0) > Version(1, 9) # True
singledispatch — Type-Based Function Overloading
Register different implementations based on the first argument’s type:
from functools import singledispatch
@singledispatch
def format_value(value):
return str(value)
@format_value.register(float)
def _(value):
return f"{value:.2f}"
@format_value.register(list)
def _(value):
return ", ".join(str(v) for v in value)
format_value(42) # "42"
format_value(3.14159) # "3.14"
format_value([1, 2, 3]) # "1, 2, 3"
Common Misconception
“functools.reduce is Pythonic.” Guido van Rossum himself wanted to remove reduce from Python 3 entirely. For most use cases, a loop or comprehension is clearer. reduce survives for the cases where cumulative application of a function is genuinely the clearest expression.
Quick Reference
| Tool | Purpose | When to Use |
|---|---|---|
lru_cache | Memoize function results | Expensive pure functions |
cache | Unbounded memoization | Bounded input domains |
partial | Pre-fill function arguments | Callbacks, configuration |
wraps | Preserve function metadata | Writing decorators |
reduce | Cumulative computation | Custom aggregation |
total_ordering | Auto-generate comparisons | Custom sortable classes |
singledispatch | Type-based dispatch | Format/serialize by type |
One thing to remember: functools is Python’s Swiss army knife for functions. lru_cache for speed, partial for convenience, wraps for proper decorators — learn these three and you’ll use them weekly.
See Also
- Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
- Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
- Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
- Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
- Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.