Python functools Exercises: Cache, Partial, Reduce
functools is about adapting functions without rewriting them. You can cache expensive results, freeze an argument, fold a sequence, or preserve metadata when wrapping a callable.
Cache a pure function
lru_cache stores results by the arguments used in the call. It works best for pure functions with hashable arguments. Mutating the returned object can leak state between callers, so return a copy or avoid caching mutable results.
from functools import lru_cache
@lru_cache(maxsize=None)
def square(n):
return n * n
print(square(4))
print(square(4))Same input, stored result
Inspect the cache instead of guessing
cache_info reports hits, misses, and the current size. Use it to confirm that repeated calls are actually reusing results. If the hit rate stays low, the cache may be adding memory without saving work.
from functools import lru_cache
@lru_cache(maxsize=None)
def double(n):
return n * 2
double(2)
double(2)
print(double.cache_info().hits)Measure what the cache does
Fix an argument with partial
partial returns a new callable with some arguments already supplied. It does not call the function immediately. Keyword arguments are useful when you want to leave earlier positional parameters open.
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square(6))A specialized callable
Fold with an explicit starting value
reduce combines items two at a time. Supplying an initializer makes the result predictable for an empty sequence and gives the accumulator a clear type. Without one, an empty sequence raises TypeError.
from functools import reduce
numbers = [1, 2, 3, 4]
print(reduce(lambda total, n: total + n, numbers, 0))Reduce with a safe start
Preserve metadata when wrapping
A decorator that returns a replacement function should usually apply wraps. It copies the wrapped function's name and docstring, which keeps logs, help output, and debugging readable.
from functools import wraps
def announce(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@announce
def greet():
"""Say hello."""
return "hi"
print(greet.__name__)Keep the original identity
Your practice checklist
- Cache pure functions with hashable arguments.
- Use cache_info to check whether the cache is helping.
- Use partial to freeze arguments, not to call immediately.
- Give reduce an initializer when empty input is possible.
- Apply functools.wraps in decorators that replace a function.
Common questions
Can lru_cache cache methods?
Yes, but self becomes part of the key. Make sure instances are hashable and that caching does not keep unwanted objects alive.
Does partial copy the function?
No. It stores a reference to the original callable and the arguments to prefill.
When is reduce less clear than a loop?
When the accumulation has several branches or needs early exit. A named loop is often easier to read than a dense reduce expression.
Practice functools
12 questions on caching, partial arguments, reduce, and wrapper metadata.
Keep reading: