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".