itertools provides memory-efficient tools for working with iterators. Here's what you need.
Infinite Iterators
from itertools import count, cycle, repeat
# Count forever
for i in count(10, 2): # 10, 12, 14, 16, ...
if i > 20:
break
print(i)
# Cycle through items
colors = cycle(["red", "green", "blue"])
for _ in range(5):
print(next(colors)) # red, green, blue, red, green
# Repeat value
for x in repeat("hello", 3):
print(x) # hello hello helloChain and Flatten
from itertools import chain
# Chain iterables together
letters = chain("abc", "def", "ghi")
print(list(letters)) # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
# Flatten nested iterables
nested = [[1, 2], [3, 4], [5, 6]]
flat = list(chain.from_iterable(nested))
print(flat) # [1, 2, 3, 4, 5, 6]Slicing Iterators
from itertools import islice
# Take first N items
nums = range(100)
first_five = list(islice(nums, 5)) # [0, 1, 2, 3, 4]
# Skip and take
middle = list(islice(nums, 10, 15)) # [10, 11, 12, 13, 14]
# With step
every_third = list(islice(nums, 0, 10, 3)) # [0, 3, 6, 9]Filtering
from itertools import filterfalse, takewhile, dropwhile
nums = [1, 4, 6, 4, 1, 0, 3, 5]
# Filter out truthy values
zeros = list(filterfalse(bool, [1, 0, 2, 0, 3])) # [0, 0]
# Take while condition is true
ascending = list(takewhile(lambda x: x < 5, nums)) # [1, 4]
# Drop while condition is true
after_small = list(dropwhile(lambda x: x < 5, nums)) # [6, 4, 1, 0, 3, 5]Grouping
from itertools import groupby
# Group consecutive items
data = "AAAABBBCCDA"
groups = [(k, list(g)) for k, g in groupby(data)]
# [('A', ['A', 'A', 'A', 'A']), ('B', ['B', 'B', 'B']), ...]
# Group by key function (data must be sorted by key first!)
people = [
{"name": "Alice", "dept": "Engineering"},
{"name": "Bob", "dept": "Engineering"},
{"name": "Carol", "dept": "Sales"},
]
people.sort(key=lambda x: x["dept"])
for dept, members in groupby(people, key=lambda x: x["dept"]):
print(f"{dept}: {[m['name'] for m in members]}")Combinations and Permutations
from itertools import combinations, permutations, product
items = ["a", "b", "c"]
# Combinations (order doesn't matter)
print(list(combinations(items, 2)))
# [('a', 'b'), ('a', 'c'), ('b', 'c')]
# Permutations (order matters)
print(list(permutations(items, 2)))
# [('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]
# Cartesian product
print(list(product([1, 2], ["a", "b"])))
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
# Repeat for power
print(list(product([0, 1], repeat=3)))
# All 3-bit binary numbersCombinations with Replacement
from itertools import combinations_with_replacement
items = ["a", "b", "c"]
print(list(combinations_with_replacement(items, 2)))
# [('a', 'a'), ('a', 'b'), ('a', 'c'), ('b', 'b'), ('b', 'c'), ('c', 'c')]Accumulate
from itertools import accumulate
import operator
nums = [1, 2, 3, 4, 5]
# Running sum
print(list(accumulate(nums))) # [1, 3, 6, 10, 15]
# Running product
print(list(accumulate(nums, operator.mul))) # [1, 2, 6, 24, 120]
# Running max
print(list(accumulate(nums, max))) # [1, 2, 3, 4, 5]
# Custom function
print(list(accumulate(nums, lambda a, b: a + b * 2)))Zip Variations
from itertools import zip_longest
a = [1, 2, 3]
b = [4, 5]
# Regular zip stops at shortest
print(list(zip(a, b))) # [(1, 4), (2, 5)]
# zip_longest fills missing values
print(list(zip_longest(a, b, fillvalue=0))) # [(1, 4), (2, 5), (3, 0)]Pairwise (Python 3.10+)
from itertools import pairwise
items = [1, 2, 3, 4, 5]
print(list(pairwise(items)))
# [(1, 2), (2, 3), (3, 4), (4, 5)]
# Before 3.10:
def pairwise_compat(iterable):
a, b = iter(iterable), iter(iterable)
next(b, None)
return zip(a, b)Batched (Python 3.12+)
from itertools import batched
items = range(10)
print(list(batched(items, 3)))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]
# Before 3.12:
def batched_compat(iterable, n):
from itertools import islice
it = iter(iterable)
while batch := tuple(islice(it, n)):
yield batchStarmap
from itertools import starmap
# Apply function to unpacked arguments
pairs = [(2, 3), (4, 5), (6, 7)]
print(list(starmap(pow, pairs))) # [8, 1024, 279936]
# Same as:
# [pow(a, b) for a, b in pairs]Tee (Copy Iterators)
from itertools import tee
nums = iter([1, 2, 3, 4, 5])
a, b = tee(nums, 2)
print(list(a)) # [1, 2, 3, 4, 5]
print(list(b)) # [1, 2, 3, 4, 5]
# Warning: tee stores elements in memory
# Don't use original iterator after teePractical Recipes
from itertools import islice, chain, groupby, accumulate
# Sliding window
def sliding_window(iterable, n):
from collections import deque
it = iter(iterable)
window = deque(islice(it, n), maxlen=n)
if len(window) == n:
yield tuple(window)
for x in it:
window.append(x)
yield tuple(window)
# Chunked iteration
def chunked(iterable, n):
it = iter(iterable)
while chunk := tuple(islice(it, n)):
yield chunk
# Flatten one level
def flatten(list_of_lists):
return chain.from_iterable(list_of_lists)
# Run-length encoding
def run_length_encode(iterable):
return [(k, len(list(g))) for k, g in groupby(iterable)]
# Unique elements preserving order
def unique(iterable):
seen = set()
for item in iterable:
if item not in seen:
seen.add(item)
yield item
# First n items or less
def take(n, iterable):
return list(islice(iterable, n))
# Nth item
def nth(iterable, n, default=None):
return next(islice(iterable, n, None), default)Memory Efficiency
from itertools import chain
# BAD: Creates intermediate list
data = []
for chunk in chunks:
data.extend(process(chunk))
# GOOD: Lazy iteration
data = chain.from_iterable(process(chunk) for chunk in chunks)
# BAD: Loads all into memory
sum([x ** 2 for x in range(10_000_000)])
# GOOD: Generator expression
sum(x ** 2 for x in range(10_000_000))Common Patterns
from itertools import count, takewhile
# Generate IDs
id_generator = count(1)
next_id = lambda: next(id_generator)
# Find first match
def first_where(iterable, predicate):
return next((x for x in iterable if predicate(x)), None)
# Interleave sequences
def interleave(*iterables):
from itertools import chain, zip_longest
sentinel = object()
for items in zip_longest(*iterables, fillvalue=sentinel):
yield from (x for x in items if x is not sentinel)
# Partition into two groups
def partition(predicate, iterable):
from itertools import filterfalse, tee
t1, t2 = tee(iterable)
return filterfalse(predicate, t1), filter(predicate, t2)itertools is about composing small, efficient building blocks. Use it when you're working with sequences and want to avoid loading everything into memory.
React to this post: