Q1What's the most concise way to build the Cartesian product of [1, 2, 3] and [A, B]?
itertools and functools — Iteration and Function Composition Toolkits
Learn combinations / product / chain / accumulate iteration recipes, fixing arguments with partial, and speeding recursion via @lru_cache memoization through examples.
itertools is a set of functions that build new iterables from existing ones (combinations, concatenation, accumulation), and functools is a set of functions that transform the function itself (argument binding, memoization). Both can replace for loop + state-tracking patterns in a single line, dramatically shrinking your code.
Iterables — values you can loop over
Most itertools functions take an "iterable" as input and return a new iterable, so it's worth pinning down what an iterable is first. An iterable (the thing you can write to the right of for x in ___:) is anything you can pass to a for loop, list(...), sum(...), or map(...). list / tuple / str / dict / set / range, plus generators you write yourself (functions using yield), all behave as iterables in Python.
list / tuple / str / dict / set / range are iterables, and so are custom generators built with yield.itertools — combination and iteration toolkit
itertools is a module gathering functions that build new iterables from existing ones. The four you'll use most are combinations (unordered combinations), product (Cartesian product over multiple sets), chain (concatenate iterables into one), and accumulate (running totals or running products). Knowing these four lets you rewrite double for loops and state-tracking loops much more cleanly.
list(...) to materialize.| Function | Input → Output | Example |
|---|---|---|
| combinations(it, k) | k-element combinations (unordered) | [A,B,C] → (A,B), (A,C), (B,C) |
| permutations(it, k) | k-element permutations (ordered) | [A,B] → (A,B), (B,A) |
| product(*its) | Cartesian product across sets | [S,M] × [red,blue] → 4 combos |
| chain(*its) | Concatenate multiple iterables | [1,2] + [3,4] → [1,2,3,4] |
| accumulate(it) | Accumulate from the left (sum by default) | [10,20,30] → [10, 30, 60] |
functools.partial — bind arguments ahead of time
functools.partial builds a new function with some arguments already locked in. When you need to pass a function with bound arguments to a callback or higher-order function, this saves you from writing one-line wrapper functions like def wrapper(...): .... Anywhere you call the same function repeatedly with slight argument variations, partial cuts wrapper noise and makes the code more readable.
from functools import partial
def format_with_unit(price, unit):
return f"{price}{unit}"
# Build a new function with the unit "USD" pre-bound
to_usd = partial(format_with_unit, unit="USD")
# Apply to individual prices — only price needs to be passed now
print(to_usd(100)) # 100USD
print(to_usd(200)) # 200USD
print(to_usd(300)) # 300USD
exp=2 on power(base, exp) and you get a square function that just needs base.functools.lru_cache — cache results with memoization
"I want to reuse the result of an expensive function called repeatedly with the same arguments" — comes up whenever a function's outputs are determined by its inputs and speed matters. Hand-rolling this means managing your own cache dict, but @lru_cache does it in a single decorator line.
@lru_cache is a decorator that caches a function's return values. When called again with the same arguments, it returns the cached value directly, skipping the recomputation. LRU (Least Recently Used — discards the entries unused for the longest) drops old cache entries so you can cap memory usage with something like maxsize=128.
Don't add to functions with side effects
@lru_cache assumes "same arguments → same return value". Apply it to functions with side effects (reading files / writing to a DB / returning the current time) or whose result varies for the same inputs, and you'll have a bug where the first result keeps coming back forever. Apply only to pure functions (same input → same output, no side effects).
Knowledge Check
Answer each question one by one.
Q2What does partial(f, x=10) return?
Q3Why does @lru_cache speed up a naive recursive Fibonacci?