Lesson 16 · 16 of 20
itertools and functools · Python
Handy tools from Python's standard library: accumulate, combinations, chain, count, islice and groupby, plus lru_cache, reduce and partial.

The `itertools` module bundles ready-made iterators. accumulate(daily_sales) gives the running totals day by day. combinations(menu, 2) produces every possible pair of dishes with no repeats and no regard for order: four dishes give 6 pairs. chain joins several sequences as if they were one.
count(500, 5) counts forever (500, 505, 510…) and, like everything in itertools, it is lazy. So you do not loop over it forever, `islice(tickets, 4)` takes just the first four, like a [:4] slice but for iterators.
`groupby` groups consecutive equal items and returns pairs of key and group. That is why INFO shows up twice in runs: the two are not next to each other. If you want one group per value, sort first with sorted().
From the `functools` module: reduce combines items two at a time until one is left (here it multiplies the successive discounts: 0.9 × 0.95 × 0.8), and `partial` creates a new function with one argument already filled in: spain(p) is the same as price_with_tax(p, rate=0.21).
`@lru_cache` stores a function's results by argument. The second call with "EU" does not run the body (no calculation message appears) and cache_info() confirms it: one hit and two misses. Only use it on functions that always return the same thing for the same arguments.
Code · main.py
from itertools import accumulate, chain, combinations, groupby, islice, countfrom functools import lru_cache, partial, reducedaily_sales = [120, 80, 150, 95, 200]print("Running total:", list(accumulate(daily_sales)))menu = ["soup", "salad", "pasta", "steak"]pairs = list(combinations(menu, 2)) # every pair, no repeatsprint(len(pairs), "combos, e.g.", pairs[:3])print("All orders:", list(chain(["#101", "#102"], ["#103"])))tickets = (f"T{n}" for n in count(500, 5)) # endless counterprint("Next tickets:", list(islice(tickets, 4))) # take only 4log = ["ERROR", "ERROR", "INFO", "WARN", "WARN", "WARN", "INFO"]runs = [(level, len(list(group))) for level, group in groupby(log)]print("Runs:", runs) # groupby joins neighbours onlydiscounts = [0.9, 0.95, 0.8] # 10%, then 5%, then 20% offprint("Combined factor:", round(reduce(lambda a, b: a * b, discounts), 3))def price_with_tax(price, rate): return round(price * (1 + rate), 2)spain = partial(price_with_tax, rate=0.21) # fix one argumentprint("With Spanish VAT:", [spain(p) for p in [19.99, 5.5, 30.0]])@lru_cache(maxsize=None)def shipping_quote(zone): print(f" (calculating {zone})") # only runs the first time return {"EU": 5.0, "US": 12.0}[zone]print("Quotes:", shipping_quote("EU"), shipping_quote("EU"), shipping_quote("US"))print("Cache:", shipping_quote.cache_info())Output · python3 main.py
Running total: [120, 200, 350, 445, 645]
6 combos, e.g. [('soup', 'salad'), ('soup', 'pasta'), ('soup', 'steak')]
All orders: ['#101', '#102', '#103']
Next tickets: ['T500', 'T505', 'T510', 'T515']
Runs: [('ERROR', 2), ('INFO', 1), ('WARN', 3), ('INFO', 1)]
Combined factor: 0.684
With Spanish VAT: [24.19, 6.65, 36.3]
(calculating EU)
(calculating US)
Quotes: 5.0 5.0 12.0
Cache: CacheInfo(hits=1, misses=2, maxsize=None, currsize=2)Key points
accumulate,combinationsandchainsave a lot of loops.countis infinite: cut it withislice.groupbyonly joins consecutive items.reducefolds a list into one value;partialfixes arguments.@lru_cacheremembers the results of pure functions.
Exercise
Use groupby on sorted(log) to count how many times each level appears in total, and accumulate with max to show the best day so far after each day.