Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Partial Functions & Caching

Apply functools.partial to fix function arguments and use caching to speed up repeated calls — a practical Python skill.

Focus: apply functools.partial and function caching

Sponsored

Have you ever written a function that always passes the same argument value—like print('Warning:', message) in ten different places? Or noticed a slow function recomputing the same result every time it's called with identical inputs? Python's functools module gives you two clean solutions: partial to pre-fill arguments, and lru_cache to memoize return values. This lesson shows you how to apply functools.partial and function caching to write less code and gain serious performance.

The problem this lesson solves

Repeating yourself in arguments bloats code and invites typos. A logger call like log('INFO', message) scattered through a module forces you to update every invocation if the log level changes. Without caching, expensive functions—database queries, API calls, or heavy computations—run the same logic over and over, wasting CPU cycles and response time. Both problems degrade maintainability and speed. functools.partial and lru_cache fix these without altering your original function's logic.

Core concept / mental model

Think of partial as a function factory. You give it an existing function and some fixed arguments; it returns a new function with those arguments already supplied. It's like ordering a coffee: instead of saying "large, black, no sugar" every time, you create a "default coffee" that remembers those choices. The new function still accepts any remaining arguments.

Function caching (memoization) remembers the outputs for specific inputs. functools.lru_cache wraps any function store return values in a dictionary, keyed by arguments. If the function is called again with the same arguments, the cached result is returned instantly—no re-execution. Image it as a notepad beside your desk; you write down answers to hard questions so you don't recalculate them.

Both tools live in the standard library—no extra install needed.

How it works step by step

Step 1: Import functools

import functools

Step 2: Create a partial function

Use functools.partial(func, *args, **kwargs) to fix some arguments. The returned partial object is callable.

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

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

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

Step 3: Apply @lru_cache to a function

Decorate a function with @functools.lru_cache(maxsize=128). The cache stores results for up to maxsize different calls. When the cache is full, the least recently used entry is evicted.

@functools.lru_cache(maxsize=32)
def fibonacci(n):
    """Return the nth Fibonacci number (naive recursion)."""
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# First call computes; second call returns cached
print(fibonacci(10))  # 55
print(fibonacci(10))  # 55 (from cache)

Step 4: Cache statistics and clearing

fibonacci.cache_info() shows hits, misses, maxsize, and currsize. fibonacci.cache_clear() wipes the cache manually.

print(fibonacci.cache_info())
# CacheInfo(hits=8, misses=11, maxsize=32, currsize=11)

Pro tip: Use @functools.lru_cache only on pure functions—functions that return the same output for the same input and have no side effects. Caching a function that prints, writes to disk, or depends on global state will give you stale or wrong results.

Hands-on walkthrough

Example 1: Pre-fill a logging function

import functools
import logging

logging.basicConfig(level=logging.INFO)

def log(level, message):
    logging.log(level, message)

info_log = functools.partial(log, logging.INFO)
error_log = functools.partial(log, logging.ERROR)

info_log("Service started")          # Logs INFO: Service started
error_log("Connection timeout")      # Logs ERROR: Connection timeout

Output (console):

INFO:root:Service started
ERROR:root:Connection timeout

Example 2: Cache expensive computation

import functools
import time

def expensive_computation(x):
    """Simulate a slow operation (e.g., model inference)."""
    time.sleep(2)
    return x * x

cached_computation = functools.lru_cache(maxsize=10)(expensive_computation)

start = time.time()
print(cached_computation(4))   # 16 (2s delay)
print("Time:", time.time() - start)

start = time.time()
print(cached_computation(4))   # 16 (instant)
print("Time:", time.time() - start)

Output (approximate):

16
Time: 2.001
16
Time: 0.000

Example 3: Combine partial and lru_cache

import functools

def multiply(a, b):
    return a * b

double = functools.partial(multiply, 2)

double = functools.lru_cache(maxsize=None)(double)

print(double(10))  # 20 (from cache on second call)
print(double(10))  # 20 (from cache)

Compare options / when to choose what

Tool Purpose When to Use Side effects?
functools.partial Pre-fill arguments You call the same function with constant arguments at many places No (new function object)
functools.lru_cache Cache return values Function is pure, called repeatedly with identical args, and is expensive No (if pure)
Manual dict caching Arbitrary caching logic Need full control over cache key, eviction, or persistence Requires manual code
functools.cache (Python 3.9+) Simpler, unbounded cache Unbounded cache is acceptable; pure functions No

Key distinction: partial does not cache—it only fixes arguments. Caching (with lru_cache or cache) speeds up repeated calls. Use them together for a perfect combination: a partially-applied function that also memoizes its results.

Troubleshooting & edge cases

  • Mutable default arguments in cached functions: lru_cache uses argument values to build the cache key. Mutable objects like lists or dicts cause errors because they're unhashable. @functools.lru_cache def process(data): # data is list ... # TypeError: unhashable type: 'list' Fix: Convert mutable to immutable (e.g., tuple(data)) inside the function or before calling.

  • Cache never evicts: If maxsize=None, the cache grows unbounded. For long-running apps, this can consume all memory. Always set a reasonable maxsize or use @functools.cache when you know caching is short-lived.

  • Side effects in cached functions: A function that prints something will print only on the first call—not on cache hits. ```python @functools.lru_cache def greet(name): print(f"Hello, {name}!") return name.upper()

greet("Alice") # prints greet("Alice") # no print, returns cached ``` Fix: Keep logging side effects outside the cached function.

  • Partial with keyword arguments: Order matters when mixing positional and keyword defaults. python def f(a, b, c): pass p = functools.partial(f, 1, c=3) # a=1 is positional, b remains free p(2) # calls f(1, 2, 3)

What you learned & what's next

You now know how to apply functools.partial and function caching in Python. You can: - Create new functions with pre-filled arguments using partial - Speed up pure functions with lru_cache and examine cache statistics - Combine both techniques for cleaner, faster code - Avoid common pitfalls like unhashable arguments or side effects in cached functions

Next lesson in your Python journey: Generators and yield statements—where you'll learn to produce values on the fly without storing entire sequences in memory. This pairs perfectly with caching to build high-performance data pipelines.

Practice recap: Write a partial function that converts Celsius to Fahrenheit (formula: F = C * 9/5 + 32) with the multiplier 9/5 fixed. Then add lru_cache to remember repeated conversions. Call it with 0, 100, 0, and check cache_info(). Did you get a hit on the second call of 0?

Practice recap

Write a partial function that converts Celsius to Fahrenheit (formula: F = C * 9/5 + 32) with the multiplier 9/5 fixed. Then add lru_cache to remember repeated conversions. Call it with 0, 100, 0, and check cache_info(). Did you get a hit on the second call of 0?

Common mistakes

  • Using functools.partial with mutable default arguments—the partial objec t will share the same mutable across calls, leading to surprising behavior.
  • Applying @lru_cache to a function that returns different values for the same input (e.g., datetime.now() or random.random)—caching gives stale, incorrect results.
  • Forgetting that lru_cache caches by positional and keyword arguments as a tuple, so func(a=1, b=2) and func(b=2, a=1) are treated as different calls and don't share a cache entry.
  • Setting maxsize=None in production without a memory limit—unbounded caching can cause MemoryError over long-running processes.

Variations

  1. Use functools.cache (Python 3.9+) for unbounded caching without the maxsize parameter—simpler but riskier.
  2. Use functools.lru_cache with typed=True to cache based on argument types separately (e.g., int(5) vs float(5)).
  3. Use third-party libraries like cachetools for advanced caching features (TTL, multi-key, etc.).

Real-world use cases

  • Pre-fill requests.get with headers and base URL using partial to avoid repeating authentication tokens in every API call.
  • Cache the result of a database query function that takes a user ID—reduce latency for repeated lookups in a web server.
  • Use partial to create specialized callback functions from a general function in event-driven systems (e.g., GUI button handlers).

Key takeaways

  • functools.partial creates a new callable with some arguments already supplied — perfect for reducing repetition.
  • @functools.lru_cache memoizes pure functions, dramatically speeding up repeated calls with the same arguments.
  • Always use pure functions (no side effects, deterministic) with lru_cache to avoid incorrect caching results.
  • Mutable arguments (like lists) can't be cached directly; convert to immutable forms (e.g., tuple).
  • Combine partial and lru_cache to get both cleaner function calls and performance gains.
  • Check cache stats with cache_info() and clear with cache_clear() for debugging or resetting the cache.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.