itertools Module¶
The itertools module provides a collection of fast, memory-efficient tools for working with iterators. These functions create iterators for efficient looping, combining, and filtering data.
Mental Model
itertools is a LEGO set for iterators — each function is a small, composable brick (chain, filter, group, slice, combine) that snaps together with others to build sophisticated data pipelines. Because every tool yields items lazily, you can process millions of elements without loading them all into memory at once.
python
import itertools
Why itertools?¶
- Memory efficient: Generates items on-demand (lazy evaluation)
- Fast: Implemented in C for performance
- Composable: Functions can be combined to build complex pipelines
- Pythonic: Replaces verbose loops with expressive one-liners
Infinite Iterators¶
These iterators run forever unless stopped.
count(start=0, step=1)¶
Generates consecutive numbers indefinitely:
```python from itertools import count
Count from 0¶
for i in count(): if i >= 5: break print(i) # 0, 1, 2, 3, 4
Count from 10 with step 2¶
for i in count(10, 2): if i >= 20: break print(i) # 10, 12, 14, 16, 18
Useful with zip¶
names = ['Alice', 'Bob', 'Charlie'] for i, name in zip(count(1), names): print(f"{i}. {name}")
1. Alice¶
2. Bob¶
3. Charlie¶
```
cycle(iterable)¶
Repeats an iterable indefinitely:
```python from itertools import cycle
colors = cycle(['red', 'green', 'blue']) for i, color in zip(range(7), colors): print(f"Item {i}: {color}")
Item 0: red¶
Item 1: green¶
Item 2: blue¶
Item 3: red¶
Item 4: green¶
Item 5: blue¶
Item 6: red¶
Practical: alternating row colors¶
rows = ['Row A', 'Row B', 'Row C', 'Row D', 'Row E'] bg_colors = cycle(['white', 'gray']) for row, bg in zip(rows, bg_colors): print(f"{row} -> {bg}") ```
repeat(elem, n=None)¶
Repeats an element n times (or forever if n is None):
```python from itertools import repeat
Repeat 5 times¶
list(repeat('hello', 5)) # ['hello', 'hello', 'hello', 'hello', 'hello']
Useful with map¶
import operator list(map(operator.mul, range(5), repeat(3))) # [0, 3, 6, 9, 12]
Forever (be careful!)¶
for i, x in zip(range(3), repeat('')): print(x) # , *, * ```
Combinatoric Iterators¶
Generate combinations and permutations.
product(*iterables, repeat=1)¶
Cartesian product (like nested for loops):
```python from itertools import product
Two iterables¶
list(product('AB', '12'))
[('A', '1'), ('A', '2'), ('B', '1'), ('B', '2')]¶
Three iterables¶
list(product([0, 1], [0, 1], [0, 1]))
[(0,0,0), (0,0,1), (0,1,0), (0,1,1), (1,0,0), (1,0,1), (1,1,0), (1,1,1)]¶
With repeat¶
list(product('AB', repeat=2))
[('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'B')]¶
Practical: generate all possible configurations¶
sizes = ['S', 'M', 'L'] colors = ['red', 'blue'] for size, color in product(sizes, colors): print(f"{size}-{color}") ```
permutations(iterable, r=None)¶
All possible orderings of length r:
```python from itertools import permutations
All permutations¶
list(permutations('ABC'))
[('A','B','C'), ('A','C','B'), ('B','A','C'),¶
('B','C','A'), ('C','A','B'), ('C','B','A')]¶
Permutations of length 2¶
list(permutations('ABC', 2))
[('A','B'), ('A','C'), ('B','A'), ('B','C'), ('C','A'), ('C','B')]¶
Count permutations: n! / (n-r)!¶
permutations('ABCD', 2) -> 4! / 2! = 12 items¶
```
combinations(iterable, r)¶
All unique subsets of length r (order doesn't matter):
```python from itertools import combinations
Choose 2 from 4¶
list(combinations('ABCD', 2))
[('A','B'), ('A','C'), ('A','D'), ('B','C'), ('B','D'), ('C','D')]¶
Choose 3 from 4¶
list(combinations('ABCD', 3))
[('A','B','C'), ('A','B','D'), ('A','C','D'), ('B','C','D')]¶
Practical: all pairs from a list¶
people = ['Alice', 'Bob', 'Charlie', 'Diana'] for p1, p2 in combinations(people, 2): print(f"{p1} meets {p2}") ```
combinations_with_replacement(iterable, r)¶
Combinations where elements can repeat:
```python from itertools import combinations_with_replacement
list(combinations_with_replacement('ABC', 2))
[('A','A'), ('A','B'), ('A','C'), ('B','B'), ('B','C'), ('C','C')]¶
Practical: dice combinations¶
dice_sums = {} for d1, d2 in combinations_with_replacement(range(1, 7), 2): total = d1 + d2 dice_sums[total] = dice_sums.get(total, 0) + 1 ```
Comparison¶
| Function | Order Matters? | Repeats Allowed? | Example (ABC, r=2) |
|---|---|---|---|
permutations |
Yes | No | AB, BA, AC, CA, BC, CB |
combinations |
No | No | AB, AC, BC |
combinations_with_replacement |
No | Yes | AA, AB, AC, BB, BC, CC |
product |
Yes | Yes | AA, AB, AC, BA, BB, BC, CA, CB, CC |
Terminating Iterators¶
These consume input iterables and produce output.
chain(*iterables)¶
Concatenates multiple iterables into one:
```python from itertools import chain
Chain lists¶
list(chain([1, 2], [3, 4], [5, 6])) # [1, 2, 3, 4, 5, 6]
Chain different types¶
list(chain('ABC', [1, 2, 3])) # ['A', 'B', 'C', 1, 2, 3]
Flatten one level of nesting¶
nested = [[1, 2], [3, 4], [5, 6]] list(chain.from_iterable(nested)) # [1, 2, 3, 4, 5, 6]
Practical: process multiple files¶
def read_lines(filename): with open(filename) as f: yield from f
all_lines = chain.from_iterable( read_lines(f) for f in ['file1.txt', 'file2.txt'] ) ```
islice(iterable, stop) or islice(iterable, start, stop, step)¶
Slice an iterator (like list slicing but for iterators):
```python from itertools import islice
First 5 elements¶
list(islice(range(100), 5)) # [0, 1, 2, 3, 4]
Elements 2-5¶
list(islice(range(100), 2, 6)) # [2, 3, 4, 5]
Every other element, first 10¶
list(islice(range(100), 0, 10, 2)) # [0, 2, 4, 6, 8]
Practical: preview large iterator¶
large_data = range(1_000_000) preview = list(islice(large_data, 5)) print(f"First 5: {preview}") ```
takewhile(predicate, iterable)¶
Takes elements while predicate is True:
```python from itertools import takewhile
Take while less than 5¶
list(takewhile(lambda x: x < 5, [1, 3, 5, 2, 4]))
[1, 3] — stops at 5, doesn't see 2, 4¶
Practical: read until blank line¶
lines = ['hello', 'world', '', 'ignored'] content = list(takewhile(lambda x: x != '', lines))
['hello', 'world']¶
```
dropwhile(predicate, iterable)¶
Drops elements while predicate is True, then yields the rest:
```python from itertools import dropwhile
Drop while less than 5¶
list(dropwhile(lambda x: x < 5, [1, 3, 5, 2, 4]))
[5, 2, 4] — starts yielding at 5¶
Practical: skip header lines¶
lines = ['# comment', '# another', 'data1', 'data2'] data = list(dropwhile(lambda x: x.startswith('#'), lines))
['data1', 'data2']¶
```
filterfalse(predicate, iterable)¶
Opposite of filter() — keeps elements where predicate is False:
```python from itertools import filterfalse
Keep odd numbers (where "is even" is False)¶
list(filterfalse(lambda x: x % 2 == 0, range(10)))
[1, 3, 5, 7, 9]¶
Compare with filter¶
list(filter(lambda x: x % 2 == 0, range(10)))
[0, 2, 4, 6, 8]¶
```
compress(data, selectors)¶
Filters data based on boolean selectors:
```python from itertools import compress
data = ['A', 'B', 'C', 'D', 'E'] selectors = [True, False, True, False, True] list(compress(data, selectors)) # ['A', 'C', 'E']
Practical: filter based on condition list¶
names = ['Alice', 'Bob', 'Charlie', 'Diana'] is_active = [True, False, True, True] active_users = list(compress(names, is_active))
['Alice', 'Charlie', 'Diana']¶
```
groupby(iterable, key=None)¶
Groups consecutive elements by key:
```python from itertools import groupby
Group by first letter (data must be sorted!)¶
data = ['apple', 'ant', 'banana', 'bear', 'cherry'] for key, group in groupby(data, key=lambda x: x[0]): print(f"{key}: {list(group)}")
a: ['apple', 'ant']¶
b: ['banana', 'bear']¶
c: ['cherry']¶
Group consecutive equal elements¶
data = [1, 1, 1, 2, 2, 3, 3, 3, 3] for key, group in groupby(data): print(f"{key}: {list(group)}")
1: [1, 1, 1]¶
2: [2, 2]¶
3: [3, 3, 3, 3]¶
Important: data must be sorted by key!¶
data = ['apple', 'banana', 'ant'] # Not sorted for key, group in groupby(data, key=lambda x: x[0]): print(f"{key}: {list(group)}")
a: ['apple']¶
b: ['banana']¶
a: ['ant'] — 'ant' is separate because data wasn't sorted!¶
```
accumulate(iterable, func=operator.add, initial=None)¶
Running accumulation (like cumulative sum):
```python from itertools import accumulate import operator
Cumulative sum¶
list(accumulate([1, 2, 3, 4, 5])) # [1, 3, 6, 10, 15]
Cumulative product¶
list(accumulate([1, 2, 3, 4, 5], operator.mul)) # [1, 2, 6, 24, 120]
Running maximum¶
list(accumulate([3, 1, 4, 1, 5, 9], max)) # [3, 3, 4, 4, 5, 9]
With initial value¶
list(accumulate([1, 2, 3], initial=10)) # [10, 11, 13, 16] ```
zip_longest(*iterables, fillvalue=None)¶
Like zip(), but continues until longest iterable is exhausted:
```python from itertools import zip_longest
Regular zip stops at shortest¶
list(zip([1, 2, 3], ['a', 'b'])) # [(1, 'a'), (2, 'b')]
zip_longest continues¶
list(zip_longest([1, 2, 3], ['a', 'b']))
[(1, 'a'), (2, 'b'), (3, None)]¶
Custom fill value¶
list(zip_longest([1, 2, 3], ['a', 'b'], fillvalue='?'))
[(1, 'a'), (2, 'b'), (3, '?')]¶
```
tee(iterable, n=2)¶
Create n independent iterators from one:
```python from itertools import tee
Create two independent iterators¶
iter1, iter2 = tee(range(5)) list(iter1) # [0, 1, 2, 3, 4] list(iter2) # [0, 1, 2, 3, 4]
Useful for pairwise iteration¶
def pairwise(iterable): a, b = tee(iterable) next(b, None) # Advance second iterator return zip(a, b)
list(pairwise([1, 2, 3, 4])) # [(1, 2), (2, 3), (3, 4)] ```
Note: Python 3.10+ has itertools.pairwise() built-in.
Practical Examples¶
Flatten Nested Lists¶
```python from itertools import chain
nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]] flat = list(chain.from_iterable(nested))
[1, 2, 3, 4, 5, 6, 7, 8, 9]¶
```
Batch Processing¶
```python from itertools import islice
def batched(iterable, n): """Yield successive n-sized chunks.""" it = iter(iterable) while batch := list(islice(it, n)): yield batch
list(batched(range(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]¶
```
Note: Python 3.12+ has itertools.batched() built-in.
Round-Robin¶
```python from itertools import cycle, islice
def roundrobin(*iterables): """Visit each iterable in turn.""" iterators = cycle(iter(it) for it in iterables) # ... (simplified version)
Example usage¶
list(islice(cycle([1, 2, 3]), 10))
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]¶
```
Generate Test Data¶
```python from itertools import product
All possible boolean combinations¶
test_cases = list(product([True, False], repeat=3))
8 test cases with all True/False combinations¶
```
Summary¶
| Category | Functions |
|---|---|
| Infinite | count, cycle, repeat |
| Combinatoric | product, permutations, combinations, combinations_with_replacement |
| Terminating | chain, islice, takewhile, dropwhile, filterfalse, compress, groupby, accumulate, zip_longest, tee |
Key Points:
- All itertools functions return iterators (lazy evaluation)
- Wrap in
list()to see results - Combine functions for powerful data pipelines
- Memory efficient for large datasets
groupbyrequires sorted input- Use
chain.from_iterableto flatten nested structures
Exercises¶
Exercise 1.
Write a function powerset that takes a list and returns a list of all subsets (the power set) using chain.from_iterable and combinations. For example, powerset([1, 2, 3]) should return [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)].
Solution to Exercise 1
```python from itertools import chain, combinations
def powerset(items): return list(chain.from_iterable( combinations(items, r) for r in range(len(items) + 1) ))
Test¶
print(powerset([1, 2, 3]))
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]¶
```
Exercise 2.
Write a function sliding_pairs that takes an iterable and returns a list of consecutive pairs using tee and islice (or zip). For example, sliding_pairs([1, 2, 3, 4]) should return [(1, 2), (2, 3), (3, 4)].
Solution to Exercise 2
```python from itertools import tee
def sliding_pairs(iterable): a, b = tee(iterable) next(b, None) return list(zip(a, b))
Test¶
print(sliding_pairs([1, 2, 3, 4]))
[(1, 2), (2, 3), (3, 4)]¶
```
Exercise 3.
Write a function batched_accumulate that takes a list of numbers and a batch size, groups the numbers into batches using islice, sums each batch, and returns a list of the cumulative batch sums using accumulate. For example, batched_accumulate([1, 2, 3, 4, 5, 6], 2) should group as [3, 7, 11] (sums of [1,2], [3,4], [5,6]) then accumulate to [3, 10, 21].
Solution to Exercise 3
```python from itertools import accumulate, islice
def batched_accumulate(numbers, batch_size): it = iter(numbers) batch_sums = [] while True: batch = list(islice(it, batch_size)) if not batch: break batch_sums.append(sum(batch)) return list(accumulate(batch_sums))
Test¶
print(batched_accumulate([1, 2, 3, 4, 5, 6], 2))
[3, 10, 21]¶
```