functools: lru_cache, partial, reduce

The functools module is a standard-library toolkit of higher-order helpers that transform or extend functions — adding caching, pre-filling arguments, folding sequences, and preserving metadata.

Learn functools: lru_cache, partial, reduce in our free Python course — an interactive lesson with runnable examples, a practice exercise and a quick reference.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

These are the tools that separate "code that works" from "code that's fast and clean." A single @lru_cache line can turn an exponential algorithm into an instant one.

The naive recursive Fibonacci re-computes the same values an astronomical number of times. One decorator fixes it by remembering every result:

Without the cache, fib(35) makes nearly 30 million calls. With it, each n is computed exactly once. The .cache_info() method reports hits, misses, and the current size. Since Python 3.9, @cache is a cleaner alias for @lru_cache(maxsize=None) :

functools.partial takes a function plus some arguments and hands back a new, specialized function with those arguments locked in. It is cleaner than a lambda and carries no surprise scope bugs:

partial shines when you need to pass a configured callback to something like map() , a button handler, or a sort key — you want a function "shape" with some arguments already decided.

reduce folds a whole sequence down to a single value by repeatedly applying a two-argument function. Often a plain loop or sum() is clearer, but for running products or custom accumulations it is the right tool:

Next, @wraps — the fix for decorators that "eat" a function's identity. Without it, the wrapped function reports the wrapper's name:

Finally, @cached_property turns an expensive computed attribute into one that runs once and then caches the value on the instance:

Replace each ___ so the program memoizes a factorial and builds a pre-filled helper. Run it and match the expected output.

Line 1 blank: lru_cache — so the decorator reads @lru_cache(maxsize=None) .

Line 2 blank: base — so the call reads partial(int, base=2) .

✅ Pass a tuple instead — tuples are hashable: total((1, 2, 3)) .

✅ Add @wraps(func) above wrapper to keep the real name and docstring.

✅ Only cache functions whose output depends solely on their arguments.

Count the number of paths through a grid from the top-left to the bottom-right, moving only right or down. The naive recursion is exponential — cache it and it is instant.

Go deeper with the official Python documentation:

Lesson complete — your functions are faster and cleaner!

You can memoize with @lru_cache and @cache , pre-fill arguments with partial , fold sequences with reduce , preserve metadata with @wraps , and cache computed attributes with @cached_property .

🚀 Up next: Enums (the enum module) — give your constants real names, values, and type safety.

Practice quiz

What does @lru_cache do to a function?

  • Runs it on a background thread
  • Limits how many times it can be called
  • Memoizes results so repeated calls with the same arguments are instant
  • Makes it run in parallel

Answer: Memoizes results so repeated calls with the same arguments are instant. @lru_cache stores each result keyed by its arguments, so later calls with the same arguments are served from the cache.

What is @cache equivalent to since Python 3.9?

  • @lru_cache(maxsize=None)
  • @lru_cache(maxsize=128)
  • @property
  • @wraps

Answer: @lru_cache(maxsize=None). @cache is a cleaner alias for @lru_cache(maxsize=None) — an unbounded cache.

What does partial(int, base=2) produce?

  • The number 2
  • A function that always returns 2
  • An error
  • A function that parses binary strings

Answer: A function that parses binary strings. partial locks base=2 into int, giving a binary parser: parse_binary("1010") returns 10.

What does reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5]) return?

  • 15
  • 120
  • 5

Answer: 120. reduce folds the list by multiplying: 1*2*3*4*5 = 120.

What does reduce(lambda acc, x: acc + x, [10, 20, 30], 100) return?

  • 160
  • 60
  • 100
  • 150

Answer: 160. The third argument 100 is the starting value: 100 + 10 + 20 + 30 = 160.

Why use @functools.wraps in a decorator?

  • To make the wrapper run faster
  • To cache the function's result
  • To copy the original function's name, docstring, and metadata onto the wrapper
  • To make the function private

Answer: To copy the original function's name, docstring, and metadata onto the wrapper. Without @wraps the wrapped function reports the wrapper's name; @wraps copies the original metadata across.

What kind of argument can NOT be passed to an @lru_cache'd function?

  • An integer
  • A list (unhashable)
  • A string
  • A tuple

Answer: A list (unhashable). Cache keys must be hashable; a list is unhashable and raises TypeError. Use a tuple instead.

What does @cached_property do?

  • Recomputes the value on every access
  • Makes the attribute read-only forever
  • Caches the value globally for all instances
  • Computes the value once and caches it on the instance

Answer: Computes the value once and caches it on the instance. @cached_property runs the computation once and stores the result on the instance's __dict__.

Which type of function is safe to cache?

  • One that reads the current time
  • A pure function whose output depends only on its arguments
  • One that hits a database
  • One that reads a file each call

Answer: A pure function whose output depends only on its arguments. Only pure functions (same output for same input, no side effects) should be cached.

After @announce wraps add, what does add.__name__ print if @wraps was used?

  • wrapper
  • announce
  • add
  • func

Answer: add. With @wraps(func) the wrapper keeps the original name, so add.__name__ is "add".