Apply Now Apply Now Apply Now
header_logo
Post thumbnail
PYTHON

Functools Deep drive: lru_cache, reduce & partial 

By Vishalini Devarajan

Most Python developers use functions every day without thinking much about the function object itself, but Python treats functions as first-class citizens, and the functools module is built entirely around that idea.

Whether you want to cache expensive computations, build decorators that behave correctly, or write a single function that works differently for different argument types, the Python functools module has a tool for it. In this blog, we walk through the most useful functions in functools: lru_cache, reduce, partial, wraps, and singledispatch, with practical code examples for each.

Table of contents


  1. TL;DR Summary
  2. lru_cache: Automatic Memoisation
    • Basic Usage
    • Cache Management
  3. reduce: Cumulative Computation
    • Basic Usage
    • Using an Initial Value
  4. partial: Pre-Filling Function Arguments
    • Basic Usage
    • Real-World Use: Configuring Callbacks
  5. wraps: Preserving Function Metadata in Decorators
    • The Problem Without wraps
    • The Fix With wraps
  6. singledispatch: Type-Based Function Overloading
    • Basic Usage
  7. Functools Module: Quick Reference
  8. Common Mistakes When Using Functools
  9. Conclusion
  10. FAQs
    •     What is the functools module in Python?
    •     What does lru_cache do in Python?
    •     What is the difference between reduce and sum in Python?
    •     What is functools.partial used for?
    •     Why do I need functools.wraps in decorators?
    •     What is functools.singledispatch?
    •     Can lru_cache be used with mutable arguments like lists?

TL;DR Summary

  • The Python functools module is a standard library toolkit for working with functions as first-class objects. 
  • Its most useful tools include lru_cache for automatic memoisation, reduce for cumulative computations, partial for pre-filling function arguments, wraps for preserving metadata in decorators, and singledispatch for type-based function overloading. 
  • Mastering the Python functools module makes your code faster, more reusable, and significantly more idiomatic.

Want to master Python fundamentals, functional programming, data structures, and real-world projects with mentorship? Check out HCL GUVI’s Python Programming Course designed for learners who want to write efficient, idiomatic Python with hands-on practice and structured guidance.

lru_cache: Automatic Memoisation

lru_cache is a decorator that automatically caches the results of a function based on its arguments. The next time the function is called with the same arguments, the cached result is returned instantly instead of being recomputed an optimisation technique called memoisation.

Basic Usage

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n – 1) + fibonacci(n – 2)

print(fibonacci(50))   # Instant, despite exponential recursion

Without lru_cache, fibonacci(50) would make over 2^50 recursive calls. With it, each unique value of n is computed only once, turning an exponential algorithm into a linear one.

Cache Management

lru_cache exposes useful introspection and control methods:

fibonacci.cache_info() # CacheInfo(hits=48, misses=51, maxsize=None, currsize=51)
fibonacci.cache_clear()   # Clear the cache entirely

The maxsize parameter controls how many results are cached. Setting maxsize=None means unlimited caching (be careful with memory usage); a numeric value uses a Least Recently Used eviction policy, discarding the oldest unused entries once the limit is reached.

Want to master Python fundamentals, functional programming, data structures, and real-world projects with mentorship? Check out HCL GUVI’s Python Programming Course designed for learners who want to write efficient, idiomatic Python with hands-on practice and structured guidance.

💡 Did You Know?

Python’s @lru_cache decorator works only with hashable arguments because it stores function calls as dictionary keys internally. Immutable types such as integers, strings, and tuples are supported, but passing mutable types like lists or dictionaries raises a TypeError since they are unhashable and cannot be used as cache keys.

Read More: What is Recursion in Python? A Beginner’s Guide

reduce: Cumulative Computation

reduce applies a function of two arguments cumulatively to the items of an iterable, reducing it to a single value. It is the functional programming equivalent of a running total but generalised to any binary operation.

Basic Usage

from functools import reduce

numbers = [1, 2, 3, 4, 5]

# Sum all numbers
total = reduce(lambda acc, x: acc + x, numbers)
print(total)   # 15

# Find the maximum
maximum = reduce(lambda acc, x: acc if acc > x else x, numbers)
print(maximum)   # 5
MDN

Using an Initial Value

reduce accepts an optional third argument an initial value for the accumulator. This is essential when the iterable might be empty, or when you want to start from a specific value.

# Concatenate strings with an initial value
words = [‘Python’, ‘is’, ‘fun’]
sentence = reduce(lambda acc, w: acc + ‘ ‘ + w, words, ‘Sentence:’)
print(sentence)   # ‘Sentence: Python is fun’

# Without initial value, an empty iterable raises TypeError
reduce(lambda a, b: a + b, [])   # TypeError: reduce() of empty sequence
reduce(lambda a, b: a + b, [], 0)  # 0 safe with initial value

In practice, most simple reductions (sum, max, min, any, all) are better expressed using Python’s built-in functions sum(), max(), min() which are faster and more readable. reduce shines when your accumulation logic is custom and does not map to a built-in.

partial: Pre-Filling Function Arguments

partial creates a new function with some arguments of the original function already filled in, known as partial application. This is useful for adapting a general-purpose function to a more specific use case without writing a wrapper function manually.

Basic Usage

from functools import partial

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

square = partial(power, exponent=2)
cube   = partial(power, exponent=3)

print(square(5))   # 25
print(cube(5)) # 125

Real-World Use: Configuring Callbacks

partial is especially common when working with APIs that expect a callback function with a fixed signature, but you need to pass extra context.

from functools import partial

def log_message(level, message):
    print(f‘[{level}] {message}’)

# Create specialised loggers
log_error = partial(log_message, ‘ERROR’)
log_info  = partial(log_message, ‘INFO’)

log_error(‘Database connection failed’)   # [ERROR] Database connection failed
log_info(‘Server started successfully’)   # [INFO] Server started successfully

wraps: Preserving Function Metadata in Decorators

When you write a decorator, the wrapper function replaces the original, which means the original function’s name, docstring, and metadata are lost unless you explicitly preserve them. functools.wraps solves this in one line. 

The Problem Without wraps

def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(name):
    ”’Returns a greeting for name.”’
    return f‘Hello, {name}!’

print(greet.__name__)   # ‘wrapper’  — wrong!
print(greet.__doc__) # None       — lost!

The Fix With wraps

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(name):
    ”’Returns a greeting for name.”’
    return f‘Hello, {name}!’

print(greet.__name__)   # ‘greet’                 — correct!
print(greet.__doc__) # ‘Returns a greeting for name.’  — preserved!

Always use @wraps when writing decorators it costs one line and prevents subtle bugs in tools that rely on function introspection, such as debuggers, documentation generators, and testing frameworks.

singledispatch: Type-Based Function Overloading

Python does not have built-in function overloading based on argument type but singledispatch from functools provides exactly that. It lets you define a generic function and register type-specific implementations.

Basic Usage

from functools import singledispatch

@singledispatch
def describe(value):
    return f‘Generic value: {value}.’

@describe.register(int)
def _(value):
    return f‘Integer: {value} (even={value % 2 == 0})’

@describe.register(str)
def _(value):
    return f‘String of length {len(value)}: “{value}”‘

@describe.register(list)
def _(value):
    return f‘List with {len(value)} items’

print(describe(42))     # Integer: 42 (even=True)
print(describe(‘hello’)) # String of length 5: “hello”
print(describe([1, 2, 3]))  # List with 3 items

singledispatch is widely used in serialisation libraries, formatters, and visitor-pattern implementations where behaviour needs to vary cleanly based on the type of input.

Functools Module: Quick Reference

FunctionPurposeCommon Use Case
lru_cacheCaches function results by argumentSpeeding up recursive or expensive pure functions
reduceCumulative fold over an iterableCustom accumulations not covered by sum/max/min
partialPre-fills function argumentsCreating specialised versions of general functions
wrapsPreserves metadata in decoratorsWriting correct, introspectable decorators
singledispatchType-based function overloadingGeneric functions with type-specific behaviour
cmp_to_keyConverts old-style comparator to keyMigrating legacy comparison functions for sort()
total_orderingFills in comparison methods from oneDefining custom classes with full ordering support

Common Mistakes When Using Functools

1. Using lru_cache on functions with mutable arguments: lru_cache requires hashable arguments. Passing a list, dict, or set raises a TypeError. Convert mutable arguments to tuples or frozensets before caching, or avoid caching functions that take mutable inputs.

2. Forgetting @wraps in custom decorators: Without @wraps, the decorated function loses its name, docstring, and signature, breaking tools like help(), debuggers, and test frameworks that rely on introspection. Always add @wraps(func) to your wrapper.

3. Reaching for reduce when a built-in exists: reduce(lambda a, b: a + b, numbers) is less readable and slower than sum(numbers). Use reduce only for genuinely custom accumulation logic that built-ins like sum, max, min, any, and all cannot express.

4. Using lru_cache on methods without considering memory: Applying lru_cache to instance methods caches results keyed on self as well, which keeps instances alive as long as the cache holds a reference, potentially causing memory leaks in long-running applications. Consider cachetools or clearing the cache explicitly when instances are no longer needed. 

Conclusion

The Python functools module is a small but powerful corner of the standard library that rewards the time you invest in it. lru_cache turns slow recursive functions into fast ones with a single decorator. reduce gives you a general tool for custom accumulations. partial lets you adapt general functions to specific contexts without boilerplate. wraps keeps your decorators well-behaved. 

And singledispatch brings clean, type-based behaviour to generic functions. The best way to internalise these is to refactor existing code: find a recursive function and add lru_cache, find a repeated wrapper pattern and extract it into a decorator with wraps, or find a place where partial could eliminate a small helper function. Each of these small refactors will make functools feel like a natural part of your Python toolkit.

FAQs

1.    What is the functools module in Python?

The functools module is part of Python’s standard library, providing higher-order functions and tools for working with functions as objects. It includes lru_cache for memoisation, reduce for cumulative computations, partial for pre-filling arguments, wraps for decorator metadata, and singledispatch for type-based overloading.

2.    What does lru_cache do in Python?

lru_cache is a decorator from functools that automatically caches a function’s return values based on its arguments. Repeated calls with the same arguments return the cached result instantly instead of recomputing, which is especially powerful for recursive functions like Fibonacci.

3.    What is the difference between reduce and sum in Python?

sum() is a built-in function specifically for adding numbers in an iterable, and is faster and more readable for that purpose. reduce() from functools is a general-purpose tool that applies any binary function cumulatively across an iterable, useful when your accumulation logic is not simple addition.

4.    What is functools.partial used for?


.partial creates a new function with some arguments of an existing function pre-filled. It is commonly used to adapt general-purpose functions to specific contexts, such as creating specialised logging functions or configuring callbacks that need extra fixed arguments.

5.    Why do I need functools.wraps in decorators?

Without functools. wraps, a decorated function loses its original name, docstring, and metadata, since the wrapper function replaces it. @wraps(func) copies this metadata onto the wrapper, ensuring tools like help(), debuggers, and documentation generators work correctly.

6.    What is functools.singledispatch?

singledispatch is a decorator that enables function overloading based on the type of the first argument. You define a generic function and register type-specific implementations using @function.register(type), allowing different behaviour for different input types without if/elif type checks.

MDN

7.    Can lru_cache be used with mutable arguments like lists?

No. lru_cache requires all arguments to be hashable because it uses them as dictionary keys internally. Passing a list, dict, or set raises a TypeError. Convert mutable arguments to tuples or frozensets if you need to cache based on their contents.

Success Stories

Did you enjoy this article?

Schedule 1:1 free counselling

Similar Articles

Loading...
Get in Touch
Chat on Whatsapp
Request Callback
Share logo Copy link
Table of contents Table of contents
Table of contents Articles
Close button

  1. TL;DR Summary
  2. lru_cache: Automatic Memoisation
    • Basic Usage
    • Cache Management
  3. reduce: Cumulative Computation
    • Basic Usage
    • Using an Initial Value
  4. partial: Pre-Filling Function Arguments
    • Basic Usage
    • Real-World Use: Configuring Callbacks
  5. wraps: Preserving Function Metadata in Decorators
    • The Problem Without wraps
    • The Fix With wraps
  6. singledispatch: Type-Based Function Overloading
    • Basic Usage
  7. Functools Module: Quick Reference
  8. Common Mistakes When Using Functools
  9. Conclusion
  10. FAQs
    •     What is the functools module in Python?
    •     What does lru_cache do in Python?
    •     What is the difference between reduce and sum in Python?
    •     What is functools.partial used for?
    •     Why do I need functools.wraps in decorators?
    •     What is functools.singledispatch?
    •     Can lru_cache be used with mutable arguments like lists?