Focused practice · 9 min read

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

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.

Open practice

Keep reading: