functools: Higher-Order Functions & Caching
The functools module is Python's toolbox for working with functions as first-class objects. It provides utilities for caching expensive computations, creating specialized versions of functions, and writing well-behaved decorators.
functools.partial โ Partial Application
partial(func, *args, **kwargs) creates a new function with some arguments pre-filled. This is called partial application โ you "partially" call a function, locking in some of its arguments.
Why Use partial Instead of a Lambda?
partial also works great for callback-based APIs where you need to pass a function with no arguments but want to encode some configuration into it.
functools.reduce โ Fold Over a Sequence
reduce(function, iterable, initializer=None) applies a two-argument function cumulatively to items of an iterable, reducing it to a single value.
Note: For simple reductions, Python's built-ins (sum(), max(), min()) are preferred. reduce() shines for custom accumulation logic.
functools.lru_cache โ Memoization Made Easy
lru_cache (Least Recently Used cache) caches the return value of a function based on its arguments. When the same arguments are passed again, it returns the cached result without re-executing the function.
maxsize Parameter
maxsize controls how many different argument combinations to remember:
Important: Arguments Must Be Hashable
lru_cache uses the arguments as a dictionary key, so they must be hashable. Lists, dicts, and sets cannot be cached directly.
functools.cache โ Simpler Unlimited Cache (Python 3.9+)
functools.cache is shorthand for lru_cache(maxsize=None). It's simpler and slightly faster because it skips the LRU bookkeeping.
Use cache when you want unlimited caching. Use lru_cache(maxsize=N) when memory is a concern and you want to limit the cache size.
functools.wraps โ Preserving Decorator Metadata
When you write a decorator, your wrapper function replaces the original. This breaks __name__, __doc__, and other metadata. wraps fixes that.
wraps copies __name__, __qualname__, __doc__, __dict__, __module__, and __annotations__ from the original function to the wrapper. This makes introspection tools, documentation generators, and debuggers work correctly.
Accessing the Wrapped Function
wraps also sets __wrapped__, which points to the original function:
Combining functools Tools
The key insight is that functools transforms functions into more powerful versions of themselves โ adding caching, parameter locking, or metadata preservation โ without changing their fundamental behavior.
Knowledge Check
What does `functools.partial(pow, 2)` create?
Why must arguments to an `lru_cache`-decorated function be hashable?
What problem does `@functools.wraps(func)` solve in decorators?