functools & Functional Programming Tools

15 questions found

What is the functools module used for in Python?

Beginner
functools provides higher-order functions that act on or return other functions, such as tools for caching (lru_cache, cache), partial application (partial), reducing sequences (reduce), and customizing comparisons (cmp_to_key, total_ordering). It centralizes common functional-programming utilities so you don't reimplement them.
import functools

@functools.lru_cache(maxsize=None)
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

print(fib(30))  # cached, fast
Real-world example A pricing service caches expensive tax-rate lookups per region using lru_cache to avoid repeated database hits.

Common follow-ups: What's the difference between functools.cache and lru_cache?;When would you avoid caching a function?

Decorators;Closures

How does functools.partial work and when is it useful?

Intermediate
partial creates a new callable with some positional or keyword arguments pre-filled, returning a function with a smaller remaining signature. It's useful for adapting a general function to a specific callback interface, like event handlers or map() calls, without writing a wrapper function or lambda.
from functools import partial

def power(base, exp):
    return base ** exp

square = partial(power, exp=2)
print(square(5))  # 25
Real-world example A GUI framework binds partial(handle_click, button_id=3) as a callback so the click handler already knows which button fired.

Common follow-ups: How is partial different from a lambda?;Can you partial a method?

Decorators;Functions & Scope

What does functools.reduce do, and why is it often discouraged in favor of loops?

Intermediate
reduce(function, iterable, initializer) cumulatively applies a binary function to items of an iterable, reducing it to a single value. It's discouraged for complex logic because the accumulation is implicit and harder to read than an explicit loop or a built-in like sum() or math.prod(), which are clearer for common cases.
from functools import reduce

product = reduce(lambda acc, x: acc * x, [1,2,3,4], 1)
print(product)  # 24
Real-world example A financial report combines a list of transaction deltas into a running total balance using reduce, though sum() would be clearer for simple addition.

Common follow-ups: When is reduce clearer than a for-loop?;What's the role of the initializer argument?

Comprehensions & Generators;Iterators & the Iterator Protocol

How does @functools.wraps preserve metadata when writing decorators?

Intermediate
When a decorator replaces a function with a wrapper, the wrapper loses the original's __name__, __doc__, and other metadata. @functools.wraps(func) applied to the wrapper copies that metadata over, so introspection tools, help(), and debuggers still show the original function's identity.
import functools

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f'Calling {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

@log_calls
def greet(): 
    '''Say hello.'''
    pass

print(greet.__name__, greet.__doc__)
Real-world example A web framework's logging decorator uses wraps so Flask's URL routing still sees the correct endpoint function name instead of 'wrapper'.

Common follow-ups: What breaks if you omit functools.wraps?;What is functools.WRAPPER_ASSIGNMENTS?

Decorators;Magic Methods & Operator Overloading

How does functools.singledispatch enable function overloading based on argument type?

Advanced
singledispatch turns a function into a generic function that dispatches to type-specific implementations registered with @func.register(Type). The base function acts as the default implementation, and Python picks the most specific matching registered implementation based on the first argument's runtime type.
from functools import singledispatch

@singledispatch
def describe(obj):
    return f'object: {obj}'

@describe.register
def _(obj: int):
    return f'integer: {obj}'

@describe.register
def _(obj: list):
    return f'list of {len(obj)} items'

print(describe(5), describe([1,2,3]))
Real-world example A serialization library uses singledispatch to convert different Python types (int, list, datetime) to JSON-compatible representations without a long if/elif chain.

Common follow-ups: How does singledispatchmethod differ for class methods?;How is dispatch resolved for subclasses?

Multiple Inheritance & MRO;Decorators

What is functools.cached_property and how does it differ from @property combined with manual caching?

Advanced
cached_property computes a value once on first access and stores it in the instance's __dict__ under the same attribute name, replacing itself so subsequent lookups skip the descriptor entirely and hit the cached value directly -- faster than a property that manually checks a cache flag each call. It requires the instance to support attribute assignment (no __slots__ conflict).
from functools import cached_property

class Report:
    def __init__(self, data):
        self.data = data

    @cached_property
    def summary(self):
        print('Computing...')
        return sum(self.data)

r = Report([1,2,3])
print(r.summary)  # Computing... 6
print(r.summary)  # 6 (no recompute)
Real-world example A data model class uses cached_property to lazily compute an expensive aggregate statistic only when first requested by a report view.

Common follow-ups: Why doesn't cached_property work with __slots__ by default?;How do you invalidate a cached_property value?

Descriptors & Properties;Memory Management & Garbage Collection

How does functools.total_ordering reduce boilerplate for comparison methods?

Intermediate
total_ordering takes a class that defines __eq__ and one of __lt__, __le__, __gt__, or __ge__, then fills in the remaining comparison methods automatically. This avoids manually writing all six rich comparison methods for classes that need full ordering support.
from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, num):
        self.num = num
    def __eq__(self, other):
        return self.num == other.num
    def __lt__(self, other):
        return self.num < other.num

print(Version(1) <= Version(2))  # True, auto-derived
Real-world example A package manager's Version class uses total_ordering to support sorting release versions without writing __le__, __gt__, and __ge__ by hand.

Common follow-ups: Why is total_ordering slower than writing all methods manually?;What happens if __eq__ is missing?

Magic Methods & Operator Overloading;Data Types & Structures

What is the difference between functools.cache and functools.lru_cache?

Beginner
functools.cache (added in Python 3.9) is a simpler, unbounded version of lru_cache -- equivalent to lru_cache(maxsize=None). Use cache when you want unlimited caching with simpler syntax, and lru_cache when you need to bound memory usage with maxsize or need typed caching.
from functools import cache

@cache
def factorial(n):
    return 1 if n <= 1 else n * factorial(n-1)

print(factorial(10))
Real-world example A configuration loader caches parsed config file contents with @cache since the file rarely changes during a program's run.

Common follow-ups: When would unbounded caching cause memory problems?;How do you clear a cached function's cache?

functools & Functional Programming Tools;Memory Management & Garbage Collection

How do you clear or inspect the cache of an lru_cache-decorated function?

Advanced
Every lru_cache-wrapped function gets cache_info() (returns hits, misses, maxsize, currsize) and cache_clear() (empties the cache) methods attached. This is useful for debugging cache effectiveness or resetting state between test runs.
from functools import lru_cache

@lru_cache(maxsize=128)
def square(x):
    return x * x

square(4); square(4); square(5)
print(square.cache_info())  # CacheInfo(hits=1, misses=2, maxsize=128, currsize=2)
square.cache_clear()
Real-world example A test suite calls cache_clear() in a pytest fixture teardown to ensure cached results from one test don't leak into another.

Common follow-ups: Is lru_cache thread-safe?;Can you cache methods with lru_cache safely?

Debugging & Profiling;functools & Functional Programming Tools

Why is caution needed when applying @lru_cache directly to instance methods?

Advanced
lru_cache stores a reference to every argument, including self, as part of the cache key. Applied to an instance method, this keeps instances alive as long as the cache holds entries, potentially causing memory leaks, and cache entries are shared across all instances by argument value rather than being per-instance.
from functools import lru_cache

class Service:
    @lru_cache(maxsize=None)
    def compute(self, x):  # caches (self, x) pairs -- keeps self alive
        return x * 2
Real-world example A long-running server process notices memory growth traced to a cached instance method holding references to short-lived request objects via self.

Common follow-ups: How can you cache per-instance without leaking with lru_cache?;What's an alternative using cached_property?

Memory Management & Garbage Collection;Descriptors & Properties

Showing 1–10 of 15