Python itertools Cheat Sheet
1 · The lesson
readThe itertools module is a set of small tools for looping: counting, joining, slicing, grouping and combining. Every tool here is lazy: it makes one item at a time, only when the loop asks for the next item, and the tool is used up after one pass. Because of this, the examples wrap each tool in list() to show all its items. For the full tutorial, see Itertools and Generators.
An iterable is anything you can loop over: a list, a string, a range, a file. An iterator is the object that gives the items one by one. next() asks it for the next item.
from itertools import (accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest)
1. Endless: always add a stop
count(), cycle() and repeat() never stop on their own. Take a fixed number of items with islice(), pair them with a finite input in zip(), or break out of the loop.
print(list(islice(count(10, 2), 3))) # [10, 12, 14] print(list(islice(cycle("ab"), 5))) # ['a', 'b', 'a', 'b', 'a'] print(list(repeat(0, 3))) # [0, 0, 0] print(list(zip(count(1), "ab"))) # [(1, 'a'), (2, 'b')]
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def islice(*_a, **_kw): print('-> islice() called') return _AutoMock('islice()') def repeat(*_a, **_kw): print('-> repeat() called') return _AutoMock('repeat()') def count(*_a, **_kw): print('-> count() called') return _AutoMock('count()') def cycle(*_a, **_kw): print('-> cycle() called') return _AutoMock('cycle()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
count(start, step) counts up forever. cycle() repeats its input forever. repeat() repeats one value, forever unless you give a count. zip() stops at the shortest input, so count(1) stops after two items here.
2. Slice and stop
print(list(islice("abcdef", 3))) # ['a', 'b', 'c'] print(list(islice("abcdef", 2, None, 2))) # ['c', 'e'] print(next(islice("abc", 1, None))) # b print(list(takewhile(str.isdigit, "12ab3"))) # ['1', '2'] print(list(dropwhile(str.isdigit, "12ab3"))) # ['a', 'b', '3'] print(list(compress("abcd", [1, 0, 1, 0]))) # ['a', 'c'] print(list(filterfalse(str.isdigit, "a1b2"))) # ['a', 'b']
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def islice(*_a, **_kw): print('-> islice() called') return _AutoMock('islice()') def takewhile(*_a, **_kw): print('-> takewhile() called') return _AutoMock('takewhile()') def dropwhile(*_a, **_kw): print('-> dropwhile() called') return _AutoMock('dropwhile()') def compress(*_a, **_kw): print('-> compress() called') return _AutoMock('compress()') def filterfalse(*_a, **_kw): print('-> filterfalse() called') return _AutoMock('filterfalse()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
| Tool | What it does |
|---|---|
islice(it, stop) or islice(it, start, stop, step) | Slices anything you can loop over, like [start:stop:step]. Negative numbers are not allowed. |
takewhile(test, it) | Gives items while the test is true, and stops at the first false one |
dropwhile(test, it) | Skips items while the test is true, then gives all the rest |
compress(data, flags) | Keeps the items whose flag in the second list is true |
filterfalse(test, it) | The opposite of filter(): keeps the items where the test is false |
3. Join and flatten
print(list(chain("ab", "cd"))) # ['a', 'b', 'c', 'd'] print(list(chain.from_iterable([[1, 2], [3]]))) # [1, 2, 3] print(list(zip_longest("ab", "xyz", fillvalue="-"))) # [('a', 'x'), ('b', 'y'), ('-', 'z')]
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) chain = _AutoMock('chain') def zip_longest(*_a, **_kw): print('-> zip_longest() called') return _AutoMock('zip_longest()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
chain() goes through several inputs one after another, without copying them into a new list. chain.from_iterable() does the same for a list of lists, which flattens it by one level. zip_longest() keeps going until the longest input ends and fills the gaps with fillvalue.
Plain zip() stops quietly at the shortest input. Pass strict=True to get an error when the lengths differ:
try: list(zip("ab", "xyz", strict=True)) except ValueError as e: print("ValueError:", e) # ValueError: zip() argument 2 is longer than argument 1
4. Pairs and chunks
print(list(pairwise([1, 4, 9]))) # [(1, 4), (4, 9)] print([b - a for a, b in pairwise([1, 4, 9])]) # [3, 5] print(list(batched("abcde", 2))) # [('a', 'b'), ('c', 'd'), ('e',)]
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def pairwise(*_a, **_kw): print('-> pairwise() called') return _AutoMock('pairwise()') def batched(*_a, **_kw): print('-> batched() called') return _AutoMock('batched()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
pairwise() gives each item with the next one, so the pairs overlap. pairwise() is useful for finding the differences between neighbouring items. batched() (Python 3.12+) cuts the input into chunks of a fixed size. The last chunk can be shorter. In Python 3.13+, batched(..., strict=True) raises ValueError if the last chunk is short.
5. Combinations
print(list(product("ab", "xy"))) # [('a', 'x'), ('a', 'y'), ('b', 'x'), ('b', 'y')] print(list(product([0, 1], repeat=2))) # [(0, 0), (0, 1), (1, 0), (1, 1)] print(len(list(permutations("abc", 2)))) # 6 print(list(combinations("abc", 2))) # [('a', 'b'), ('a', 'c'), ('b', 'c')] print(list(combinations_with_replacement("ab", 2))) # [('a', 'a'), ('a', 'b'), ('b', 'b')]
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def product(*_a, **_kw): print('-> product() called') return _AutoMock('product()') def combinations(*_a, **_kw): print('-> combinations() called') return _AutoMock('combinations()') def combinations_with_replacement(*_a, **_kw): print('-> combinations_with_replacement() called') return _AutoMock('combinations_with_replacement()') def permutations(*_a, **_kw): print('-> permutations() called') return _AutoMock('permutations()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
| Tool | Gives you |
|---|---|
product(a, b) | Every pairing of an item from a with an item from b, the same as nested for loops |
product(x, repeat=2) | Every pairing of x with itself. With [0, 1], every on/off pattern |
permutations(x, 2) | Every ordered pick of 2: ('a', 'b') and ('b', 'a') both count |
combinations(x, 2) | Every pick of 2 where order does not matter: no ('b', 'a') |
combinations_with_replacement(x, 2) | Like combinations, but an item can be picked again |
6. Running totals
accumulate() gives the result so far after each item.
from operator import mul print(list(accumulate([1, 2, 3, 4]))) # [1, 3, 6, 10] print(list(accumulate([3, 1, 5, 2], max))) # [3, 3, 5, 5] print(list(accumulate([1, 2, 3], mul))) # [1, 2, 6] print(list(accumulate([5, -2], initial=100))) # [100, 105, 103]
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def accumulate(*_a, **_kw): print('-> accumulate() called') return _AutoMock('accumulate()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
By default it adds: a running sum. Pass a function to change that: max gives the running maximum, mul the running product. initial= puts a starting value first, such as an opening bank balance.
7. Group runs
groupby() groups runs of equal items that sit next to each other. It gives pairs of (key, group), and each group is an iterator.
print([(k, len(list(g))) for k, g in groupby("aaabca")]) # [('a', 3), ('b', 1), ('c', 1), ('a', 1)] print([k for k, g in groupby(sorted("abab"))]) # ['a', 'b']
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def groupby(*_a, **_kw): print('-> groupby() called') return _AutoMock('groupby()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
The 'a' at the end is a new group, because it is not next to the first run. To get one group per key, sort on the same key first:
words = ["fig", "kiwi", "plum", "pea"] words.sort(key=len) print({n: list(g) for n, g in groupby(words, len)}) # {3: ['fig', 'pea'], 4: ['kiwi', 'plum']}
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def groupby(*_a, **_kw): print('-> groupby() called') return _AutoMock('groupby()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
8. Map, copy, fold
from functools import reduce from operator import itemgetter print(list(starmap(pow, [(2, 3), (3, 2)]))) # [8, 9] print(reduce(mul, [2, 3, 4])) # 24 print(sorted([("a", 2), ("b", 1)], key=itemgetter(1))) # [('b', 1), ('a', 2)] a, b = tee(iter([1, 2, 3])) next(b, None) print(list(zip(a, b))) # [(1, 2), (2, 3)]
setup added so this can run · re-runs 2 earlier blocks on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def tee(*_a, **_kw): print('-> tee() called') return _AutoMock('tee()') mul = _AutoMock('mul') def starmap(*_a, **_kw): print('-> starmap() called') return _AutoMock('starmap()') def accumulate(*_a, **_kw): print('-> accumulate() called') return _AutoMock('accumulate()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest from operator import mul except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', 'from operator import mul\n\nprint(list(accumulate([1, 2, 3, 4]))) # [1, 3, 6, 10]\nprint(list(accumulate([3, 1, 5, 2], max))) # [3, 3, 5, 5]\nprint(list(accumulate([1, 2, 3], mul))) # [1, 2, 6]\nprint(list(accumulate([5, -2], initial=100))) # [100, 105, 103]', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
starmap() unpacks each tuple into the function's arguments: pow(2, 3), then pow(3, 2). reduce() combines all the items into one value. itemgetter(1) picks item 1 of each tuple, a quick key function for sorting. tee() makes two independent copies of one iterator, so each copy can move on its own. Here b moves one step ahead, so zip(a, b) pairs each item with the next one.
9. Built-in partners
These are not in itertools, but you use them in the same way.
print(list(enumerate("ab", start=1))) # [(1, 'a'), (2, 'b')] print(dict(zip("ab", [1, 2]))) # {'a': 1, 'b': 2} print(list(zip(*[(1, "a"), (2, "b")]))) # [(1, 2), ('a', 'b')] print(list(filter(None, [0, 1, "", "x"]))) # [1, 'x'] print(sum(x * x for x in range(4))) # 14 print(any(n > 3 for n in [1, 5, 2])) # True print(all(n > 0 for n in [])) # True print(next((n for n in [1, 6, 8] if n > 5), None)) # 6
zip(*rows) turns rows into columns ("unzip"). filter(None, ...) drops 0, "", None and other values that count as false. sum(x * x for x in ...) uses a generator expression (like a list comprehension, but in round brackets). It makes the items one at a time, so no list is built. any() stops at the first True. all() of an empty input is True. next(..., None) gives the first match, or None if there is no match.
10. Recipes
print(list(dict.fromkeys("abcab"))) # ['a', 'b', 'c'] print(list(zip(*[iter(range(6))] * 3))) # [(0, 1, 2), (3, 4, 5)] s = "abcd" print(list(zip(s, s[1:], s[2:]))) # [('a', 'b', 'c'), ('b', 'c', 'd')]
dict.fromkeys() drops duplicates and keeps the order. The zip(*[iter(...)] * 3) line makes chunks of 3 in Python before 3.12, but it drops any leftover items. The last line is a sliding window of 3 over a list or string.
For any iterable, not only lists, this generator function (a function that uses yield) keeps the first copy of each item. key= decides which items count as the same:
def unique_everseen(items, key=None): seen = set() for x in items: k = x if key is None else key(x) if k not in seen: seen.add(k) yield x print(list(unique_everseen("AAbBC", key=str.lower))) # ['A', 'b', 'C']
Common mistakes
Using an iterator twice. An iterator is used up after one pass, so the second list() is empty:
rows = map(str, [1, 2]) print(list(rows)) # ['1', '2'] print(list(rows)) # []
Fix: make a list once (rows = list(map(str, nums))) and reuse the list.
groupby without sorting. The same key comes back as a new group: [k for k, g in groupby("abab")] gives ['a', 'b', 'a', 'b']. Fix: sort on the same key first.
Keeping groupby groups for later. Each group g is empty once the loop moves on to the next key:
groups = [g for k, g in groupby("aab")] print([list(g) for g in groups]) # [[], []] print([list(g) for k, g in groupby("aab")]) # [['a', 'a'], ['b']]
setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def groupby(*_a, **_kw): print('-> groupby() called') return _AutoMock('groupby()') # Earlier blocks on this page that this one builds on, re-run quietly first. try: from itertools import accumulate, batched, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest except Exception: pass _pm_earlier = [ 'from itertools import (accumulate, batched, chain, combinations,\n combinations_with_replacement, compress, count, cycle,\n dropwhile, filterfalse, groupby, islice, pairwise,\n permutations, product, repeat, starmap, takewhile, tee,\n zip_longest)', ] def _pm_run_earlier(_srcs, _g): import sys as _s, io as _io, warnings as _w for _src in _srcs: _o, _e = _s.stdout, _s.stderr _s.stdout = _s.stderr = _io.StringIO() try: with _w.catch_warnings(): _w.simplefilter('ignore') exec(compile(_src, '<earlier block>', 'exec'), _g) except (Exception, SystemExit): pass finally: _s.stdout, _s.stderr = _o, _e if 'matplotlib.pyplot' in _s.modules: _s.modules['matplotlib.pyplot'].close('all') # their plots belong to their own blocks _pm_run_earlier(_pm_earlier, globals()) del _pm_earlier, _pm_run_earlier
Fix: call list(g) inside the loop.
Printing an iterator. print(chain("ab")) shows <itertools.chain object at ...>, not the items. Fix: print(list(chain("ab"))).
list() of an endless iterator. list(count()) never ends and fills the memory. Fix: use islice(), or break out of a loop.
Indexing an iterator. chain([1, 2])[0] raises TypeError, because iterators have no positions. Fix: use list(...)[0] or next(islice(it, 0, None)).
Expecting equal items to be skipped. combinations() picks by position, not by value, so equal items still form a pair: list(combinations([1, 1], 2)) is [(1, 1)].
See also
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code did, as well as whether a test passed.
Found this useful? Share it with a friend who’s learning Python.