Flatten with itertools.chain
`itertools.chain` lazily concatenates several iterables into a single one without copying their elements, which is the right tool for flattening a list of lists by exactly one level. It works on any iterable (lists, tuples, generators, file objects), so it composes cleanly with the rest of the iterator toolbox. This entry covers `chain`, the unpacking-friendly `chain.from_iterable`, and how it differs from a recursive deep flatten.
960 views
15
from itertools import chain
first = [1, 2, 3]
second = (4, 5)
third = range(6, 9)
merged = list(chain(first, second, third))
print(merged)
# [1, 2, 3, 4, 5, 6, 7, 8]
# chain itself is a lazy iterator, not a list
stream = chain('ab', 'cd')
print(next(stream), next(stream), next(stream)) # a b c
print(list(stream)) # ['d']chain(*iterables) walks the first iterable to exhaustion, then the next, and so on, yielding each element exactly once. It accepts any iterable, so you can mix lists, tuples, ranges, and strings without converting them first. Because the result is a lazy iterator, calling next() advances it and a list(...) cast materializes whatever is left. Use chain whenever you find yourself writing a + b + c and one of the operands is not a list, since chain skips the intermediate copies.
from itertools import chain
rows = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
# Wrong: chain treats the outer list as a single iterable.
print(list(chain(rows)))
# [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
# Right: from_iterable unpacks the outer level for you.
flat = list(chain.from_iterable(rows))
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Equivalent unpacking shortcut, slightly slower for huge inputs.
print(list(chain(*rows)))Calling chain(rows) with a single iterable-of-iterables yields the outer iterable itself, which is rarely what you want. chain.from_iterable(rows) is the canonical 'flatten exactly one level' helper: it lazily walks each inner iterable in turn. The chain(*rows) form does the same thing but materializes the argument tuple, so prefer from_iterable when the outer container is huge or itself a generator. This is the right pattern for flattening pages of API results, batched rows from a database cursor, or per-file tokens from a directory walk.
from itertools import chain
nested = [1, [2, [3, 4]], [5, [6, [7]]]]
# chain.from_iterable does not recurse: it tries to iterate each top-level item.
# Integer 1 is not iterable, so iterating raises TypeError.
try:
list(chain.from_iterable(nested))
except TypeError as exc:
print('TypeError:', exc)
# For arbitrary depth you have to write a recursive generator.
def deep_flatten(items):
for item in items:
if isinstance(item, list):
yield from deep_flatten(item)
else:
yield item
print(list(deep_flatten(nested)))
# [1, 2, 3, 4, 5, 6, 7]chain.from_iterable only removes one level of nesting and assumes every top-level item is iterable. If your data is a ragged tree (mixed scalars and sub-lists, arbitrary depth), chain is the wrong tool and will raise TypeError on the first scalar. The standard fix is a small recursive generator that uses yield from for sub-lists and yield for leaves. Pick the simplest tool that fits: chain for predictable two-level data, recursion only when depth varies.
