takewhile, dropwhile, compress and starmap

itertools · Advanced · 10 min

These cover the remaining loop shapes, so most stream logic can be expressed without manual state.

Filtering by condition:

  • takewhile(pred, it) yields items while pred is true, then stops for good (it consumes the first failing item).
  • dropwhile(pred, it) skips items while pred is true, then yields everything after, without testing again.
  • filterfalse(pred, it) is the complement of the built-in filter.
  • compress(data, selectors) keeps items whose matching selector is truthy, handy with a precomputed mask.

Transforming:

  • starmap(func, it) calls func(*args) for each tuple: starmap(pow, [(2, 5), (3, 2)]) gives 32, 9. It's map for pre-zipped arguments.
  • zip_longest(a, b, fillvalue=None) pairs items like zip but continues to the longest input, filling gaps.

These differ from comprehensions in two ways: they're lazy, and takewhile and dropwhile express "until" logic that a comprehension can't, since a comprehension always scans everything.

A frequent gotcha: takewhile silently swallows the first element that fails the predicate. If you need that element, use a manual loop or the before_and_after recipe from the docs.

Also note the built-in zip(..., strict=True) (3.10+) raises if lengths differ, the opposite of zip_longest.

Example

from itertools import compress, dropwhile, filterfalse, starmap, takewhile, zip_longest

readings = [3, 5, 9, 12, 4, 2]
print(list(takewhile(lambda x: x < 10, readings)))   # [3, 5, 9]
print(list(dropwhile(lambda x: x < 10, readings)))   # [12, 4, 2]
print(list(filterfalse(lambda x: x % 2, readings)))  # [12, 4, 2]

log = ["# header", "# more", "data1", "# note", "data2"]
print(list(dropwhile(lambda l: l.startswith("#"), log)))
# ['data1', '# note', 'data2']

print(list(compress("abcdef", [1, 0, 1, 0, 0, 1])))  # ['a', 'c', 'f']
print(list(starmap(pow, [(2, 5), (3, 2)])))          # [32, 9]
print(list(zip_longest("ab", [1, 2, 3], fillvalue="-")))
# [('a', 1), ('b', 2), ('-', 3)]

it = iter([1, 2, 30, 4])
print(list(takewhile(lambda x: x < 10, it)), list(it))   # [1, 2] [4]

Remember

  • takewhile/dropwhile express 'until' logic lazily
  • compress filters by a selector mask; starmap applies functions to argument tuples
  • takewhile consumes and discards the first failing item

Watch out for

  • Losing the first non-matching item from a shared iterator after takewhile.

Check yourself

  1. What does list(dropwhile(lambda x: x < 3, [1, 4, 2, 5])) 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/itertools.html#itertools.takewhile