← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Python functools Module

Python Python 3.2+ Intermediate
debt(d6/e3/b3/t5)
d6 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7), but nudged to d6 because flake8-functions and a custom code_pattern regex can catch the missing-wraps case, while lru_cache misuse (unhashable args, memory leaks) stays silent until runtime. Specialist plugins catch some cases but not the subtle memoization bugs.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). quick_fix is adding @functools.wraps(func) or swapping a lambda for partial — a small pattern replacement, occasionally requiring adding maxsize= to lru_cache, still localised to the decorator/cache definition.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). Although applies_to spans web/cli/library/queue, each functools use (a decorator, a partial, a cache) is a localised choice; missing wraps only taxes introspection/framework routing around that decorator rather than shaping the whole system.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5). The misconception (functools is a niche grab-bag) plus documented gotchas — decorators silently losing __name__ without wraps, lru_cache caching stale/leaking results — are traps most devs eventually learn but the naive path gets wrong.

About DEBT scoring →

Also Known As

functools functools.wraps functools.partial lru_cache

TL;DR

The functools module provides higher-order tools like wraps, partial, lru_cache, reduce, and singledispatch for building and reusing function logic.

Explanation

functools is Python's standard-library toolkit for working with callables as first-class values. Its most important members solve real problems that arise when you wrap, adapt, or memoize functions.

functools.wraps is the single most important tool for anyone writing decorators. A naive decorator replaces the wrapped function with an inner closure, silently destroying __name__, __doc__, __module__, and __wrapped__. Applying @functools.wraps(func) to the inner function copies that metadata across, so introspection, help(), and debuggers still see the original function.

functools.partial performs partial application: it binds some arguments now and returns a new callable that takes the rest later. This is cleaner than a lambda for pre-filling configuration, callbacks, or dependency arguments, and unlike a lambda it is picklable and carries .func, .args, and .keywords attributes.

functools.lru_cache (and the simpler cache in 3.9+) memoizes a pure function's return value keyed on its arguments, turning repeated expensive calls into dictionary lookups. Arguments must be hashable, and you should never cache functions with side effects or unbounded key spaces without a maxsize.

functools.reduce folds an iterable into a single value with a binary function; often a comprehension or sum() reads better, but reduce is right for genuine accumulations. functools.singledispatch turns a function into a type-dispatched generic, registering separate implementations per argument type - a clean alternative to isinstance chains. cached_property memoizes an instance attribute computed once per object.

The theme across all of these is reuse of function behaviour without rewriting it: wrap it (wraps), pre-configure it (partial), remember its results (lru_cache), or specialize it by type (singledispatch). Reaching for functools first keeps decorator and adapter code correct and idiomatic instead of hand-rolled and buggy.

Common Misconception

functools is just a grab-bag of niche utilities you rarely need. In reality functools.wraps is mandatory for every well-behaved decorator, and partial/lru_cache/singledispatch solve everyday wrapping, configuration, and caching problems.

Why It Matters

Without functools.wraps, decorators break introspection, logging, and framework routing that relies on __name__; partial and lru_cache remove boilerplate and expensive recomputation in production hot paths.

Common Mistakes

  • Writing a decorator without @functools.wraps, so the wrapped function loses its name, docstring, and signature.
  • Using lru_cache on a method or function with side effects or unhashable arguments, causing stale results or TypeError.
  • Applying lru_cache without a maxsize on unbounded input, leaking memory as the cache grows forever.
  • Reaching for functools.reduce where a plain sum(), any(), or comprehension would be clearer and faster.
  • Using a lambda to pre-fill arguments when functools.partial is picklable and more introspectable.

Avoid When

  • Caching a function with side effects or non-deterministic output with lru_cache, where stale results cause bugs.
  • Using reduce when a comprehension, sum(), any(), or all() expresses the intent more readably.
  • Applying lru_cache to instance methods that hold references, keeping objects alive and leaking memory.

When To Use

  • Writing any decorator - always wrap the inner function with functools.wraps.
  • Pre-binding configuration or callback arguments where partial is clearer and picklable versus a lambda.
  • Memoizing pure, expensive, deterministic functions with hashable arguments via lru_cache or cache.
  • Replacing isinstance dispatch chains with functools.singledispatch for type-based generic functions.

Code Examples

✗ Vulnerable
import time

# Decorator loses metadata; caching hand-rolled and unbounded
def timed(func):
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(func.__name__, time.perf_counter() - start)
        return result
    return wrapper

_cache = {}
def fib(n):
    if n in _cache:
        return _cache[n]
    _cache[n] = n if n < 2 else fib(n - 1) + fib(n - 2)
    return _cache[n]

@timed
def greet(name):
    """Say hello."""
    return f'Hello {name}'

print(greet.__name__)  # 'wrapper' - metadata lost
print(greet.__doc__)   # None
✓ Fixed
import functools
import time

def timed(func):
    @functools.wraps(func)  # preserves __name__, __doc__, __wrapped__
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(func.__name__, time.perf_counter() - start)
        return result
    return wrapper

@functools.lru_cache(maxsize=None)  # memoized, no manual dict
def fib(n: int) -> int:
    return n if n < 2 else fib(n - 1) + fib(n - 2)

@timed
def greet(name: str) -> str:
    """Say hello."""
    return f'Hello {name}'

# partial application instead of a lambda
log_error = functools.partial(print, '[ERROR]')

print(greet.__name__)  # 'greet' - metadata preserved
print(greet.__doc__)   # 'Say hello.'
log_error('disk full')

Added 4 Jul 2026
Views 26
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 5 pings S 0 pings S 3 pings M 2 pings T 0 pings W 1 ping T 0 pings F 1 ping S 1 ping S 0 pings M 0 pings T 1 ping W
ChatGPT 1
No pings yesterday
Google 5 ChatGPT 3 Perplexity 1 Ahrefs 1 Brave Search 1 Applebot 1 Meta AI 1 PetalBot 1
crawler 14
🧱 FUNDAMENTALS — new to this? Start with the ground floor.
Python general Python is a programming language known for readable syntax and versatility, used for web development, data science, automation, and more.

Python's gentle learning curve makes it an ideal first language, while its vast ecosystem keeps it relevant for machine learning, APIs, and DevOps. Skills transfer directly to professional environments because Python runs in production at companies of every size.

💡 When Python throws IndentationError, check that every block uses the same whitespace style—pick spaces (preferably 4) and stick with them everywhere.

Ask Codex about Python →
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Low
⚡ Quick Fix
Add @functools.wraps(func) to every decorator's inner function; use functools.partial instead of lambdas for argument binding and lru_cache(maxsize=...) for pure expensive calls.
📦 Applies To
python 3.2 web cli library queue-worker
🔗 Prerequisites
🔍 Detection Hints
def\s+\w+\(func\):\s*\n\s*def\s+wrapper\([^)]*\):(?![\s\S]*functools\.wraps)
Auto-detectable: ✓ Yes ruff (W-family only via custom/flake8 plugins) flake8-functions
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Low Context: Function


✓ schema.org compliant