DMarlon Studio

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.

Python · itertools and functools in DMarlon Studio: the example's code and its output in the terminal

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, combinations and chain save a lot of loops.
  • count is infinite: cut it with islice.
  • groupby only joins consecutive items.
  • reduce folds a list into one value; partial fixes arguments.
  • @lru_cache remembers 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.