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

ToolPurposeWhen to Use
lru_cacheMemoize function resultsExpensive pure functions
cacheUnbounded memoizationBounded input domains
partialPre-fill function argumentsCallbacks, configuration
wrapsPreserve function metadataWriting decorators
reduceCumulative computationCustom aggregation
total_orderingAuto-generate comparisonsCustom sortable classes
singledispatchType-based dispatchFormat/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.

pythonstandard-libraryfunctional

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.