Folding sequences with reduce

functools · Essentials · 7 min

reduce expresses folds like products, merges and compositions that have no dedicated built-in.

reduce(function, iterable, initial) applies a two-argument function cumulatively: it combines the first two items, then that result with the third, and so on, returning a single value.

reduce(operator.mul, [1, 2, 3, 4])   # ((1*2)*3)*4 = 24

The initial value is placed before the items. Supply it whenever the iterable might be empty: without it, reduce on an empty iterable raises TypeError. It also sets the result type, for example reduce(merge, dicts, {}).

Many reductions have clearer built-ins, which is why reduce was moved out of the builtins in Python 3:

  • sums: sum(), products: math.prod()
  • max(), min(), any(), all()
  • joining strings: "".join()
  • merging dicts: {**a, **b} or a | b

Use reduce for folds without a built-in: combining sets with operator.or_, deep getattr chains, composing a list of functions, or applying a sequence of transformations to a value.

If you need every intermediate result, not just the last, use itertools.accumulate. If the lambda you pass gets complicated, an explicit loop is usually easier to read.

Example

import operator
from functools import reduce

print(reduce(operator.mul, [1, 2, 3, 4]))          # 24
print(reduce(operator.or_, [{1, 2}, {2, 3}, {4}])) # {1, 2, 3, 4}
print(reduce(operator.add, [], 0))                 # 0
try:
    reduce(operator.add, [])
except TypeError as e:
    print("empty without initial")                 # empty without initial

def compose(*funcs):
    return reduce(lambda f, g: lambda x: g(f(x)), funcs)
pipeline = compose(str.strip, str.lower, str.title)
print(pipeline("  hELLO wORLD "))                   # Hello World

config = {"db": {"primary": {"host": "10.0.0.5"}}}
print(reduce(operator.getitem, ["db", "primary", "host"], config))  # 10.0.0.5

Remember

  • reduce(f, items, initial) folds items into one value left to right
  • Always pass an initial value if the iterable may be empty
  • Prefer sum, math.prod, max, any or join when they fit

Watch out for

  • Using reduce with a complex lambda where a for-loop would be clearer.

Check yourself

  1. What does reduce(lambda a, b: a * 10 + b, [1, 2, 3]) return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/functools.html#functools.reduce

Builds on

Unlocks

Nothing builds on this yet.