Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

Caching Function Results with functools

Learn when function-result caching is useful, how functools.cache and lru_cache reuse results, how to inspect or clear a cache, and which functions should not be cached.

Levels: Intermediate ~18 min read

Intermediate

When a function receives the same input again, Python normally runs its body again. Function-result caching remembers a result for a set of arguments so a later matching call can reuse it. This technique is often called memoization. It is useful when the work is worth reusing, but it is not a general speed switch for every function.

Cache only results that can be reused

The functools module provides @cache for an unbounded cache and @lru_cache for a cache that can limit its size. Both are decorators: they return a callable that looks up previous results before deciding whether the wrapped function needs to run.

Interactive Runnable example
Edit

Ready to run.

The first call records a miss and computes the normalized string. The second call uses the same argument, so it is a cache hit. The function is deliberately deterministic: the same input always produces the same result.

Choose between cache and lru_cache

@cache is the convenient choice when retaining every distinct argument/result pair is acceptable. @lru_cache(maxsize=...) keeps only a bounded number of recent entries. When the cache is full, an older least-recently-used entry can be evicted to make room for a new one.

Interactive Runnable example
Edit

Ready to run.

A bounded cache is often a safer default for long-running programs with many possible inputs. The cache_info() result reports hits, misses, the capacity, and the current number of entries; use those measurements rather than guessing whether caching helps.

Quick check

Which decorator creates a cache with no entry-count limit?

from functools import cache, lru_cache

@
def label(value):
    return value.upper()

label("bread")
print(label.cache_info().maxsize)

Clear results when their inputs no longer describe reality

A cache does not know when a database, configuration dictionary, file, or other source of information changes. If an answer depends on changeable external state, a stored result can become stale. The wrapped function provides cache_clear() to discard its stored entries when you have a clear reason to invalidate them.

Interactive Runnable example
Edit

Ready to run.

This example is intentionally tied to external data to show the invalidation problem. In production, prefer a design where the function’s result follows from its arguments alone, or make cache invalidation an explicit part of the program.

Quick check

Which expression clears the old rate before reading the updated value?

from functools import cache
rates = {"north": 4.0}
@cache
def rate(region):
    return rates[region]
rate("north")
rates["north"] = 5.0
print()

Know the limits before adding a cache

Cache arguments must be hashable because the cache uses them as dictionary keys. A list or dictionary cannot be used directly as a cached argument; a tuple of hashable values may work instead. Distinct argument patterns may produce distinct entries, even when they look equivalent to a person.

Quick check

Which value is rejected because it cannot be hashed for a cache key?

from functools import cache
@cache
def count_items(items):
    return len(items)
def describe(value):
    try:
        count_items(value)
    except TypeError:
        return "unhashable"
    return type(value).__name__
print(describe())

Do not cache functions when each call must perform a side effect, read the current time, generate fresh random data, or return a newly independent mutable object. Cached return values are reused too, so callers can accidentally share and mutate one list or dictionary. Caching generators and asynchronous functions is also usually inappropriate because those calls are expected to create fresh execution objects.

Interactive Runnable example
Edit

Ready to run.

The list example is a deliberate warning, not a recommended pattern: both calls receive the same cached list object. Use caching for stable, reusable results, and keep caching out when object freshness or side effects are part of the function’s contract.

Check whether the cache is helping

Use cache_info() to inspect reuse and cache_clear() to reset it. Caching trades memory and extra lookup work for avoiding repeated computation, so measure it with representative inputs. A cache can preserve argument and result objects in memory until entries are evicted or cleared.

Interactive Runnable example
Edit

Ready to run.

Keep the ownership boundaries clear: recursion explains how calls solve smaller versions of a problem, the Decorators lesson explains decorator mechanics, and this lesson owns when to cache function results and how to use the standard-library cache APIs.

Quick check

Before adding a cache, ask three things: are the arguments hashable, can the result safely be reused, and is there a deliberate policy for stale entries and memory use? Then test cache hits and misses with realistic calls.

When you are ready, take the quiz and complete the separate function-caching coding challenges.

Was this page helpful?

Mark this learning resource complete to track your learning progress.