Python Comprehensions and Iteration Tools
AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.
“Create an empty list, loop, append” compresses into a single line with a comprehension. This chapter covers comprehensions plus the standard library helpers that remove most remaining loops.
Environment
Examples are verified on Python 3.13. itertools ships with the standard library.
List comprehensions
numbers = [1, 2, 3, 4, 5]
squares = [n * n for n in numbers] # [1, 4, 9, 16, 25]
evens = [n for n in numbers if n % 2 == 0] # [2, 4]
The condition filters; a conditional expression transforms:
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ['odd', 'even', 'odd', 'even', 'odd']
Nested loops keep the same order as the equivalent for statements, outermost first:
pairs = [(x, y) for x in range(3) for y in range(2)]
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
Dict and set comprehensions
names = ["python", "go", "rust"]
lengths = {name: len(name) for name in names}
# {'python': 6, 'go': 2, 'rust': 4}
inverted = {v: k for k, v in lengths.items()}
adults = {name: age for name, age in ages.items() if age >= 18}
unique_lengths = {len(name) for name in names} # {2, 4, 6}
Inverting a dict silently loses data when values collide; check uniqueness first if the values are not guaranteed distinct.
Generator expressions
Parentheses instead of brackets produce a lazy generator that computes values on demand:
total = sum(n * n for n in range(10_000_000)) # no giant list in memory
A generator can be consumed once. If you need it twice, materialize it into a list or wrap the logic in a function that creates a fresh generator per call.
For a single argument call, the extra parentheses are optional: sum(x for x in xs).
Built-in iteration helpers
for i, name in enumerate(names, start=1):
print(i, name)
for name, score in zip(names, scores):
print(name, score)
zip silently truncates to the shorter input. When the lengths must match, pass strict=True (3.10+):
list(zip(names, scores, strict=True)) # raises ValueError on mismatch
Sorting and extremes:
sorted(words, key=len) # by length
sorted(words, key=str.lower) # case-insensitive
sorted(users, key=lambda u: u.age, reverse=True)
max(words, key=len) # longest word
Deduplication with order, reversal, joining:
list(dict.fromkeys(items)) # dedupe, keeps first occurrence order
list(reversed(items)) # iterate backwards without copying
", ".join(names)
any / all short-circuit, so they pair well with generators:
any(n > 100 for n in numbers) # stops at the first match
all(n > 0 for n in numbers) # stops at the first failure
itertools
from itertools import chain, islice, count, groupby, combinations
list(chain([1, 2], [3, 4])) # [1, 2, 3, 4]
list(islice(range(100), 5)) # [0, 1, 2, 3, 4], no list built
list(islice(count(10, 2), 3)) # [10, 12, 14], first 3 of an infinite sequence
list(combinations("abc", 2)) # [('a', 'b'), ('a', 'c'), ('b', 'c')]
groupby groups adjacent items with the same key, so sort by that key first:
rows = [("a", 1), ("a", 2), ("b", 3)]
for key, group in groupby(rows, key=lambda r: r[0]):
print(key, list(group))
# a [('a', 1), ('a', 2)]
# b [('b', 3)]
The group is an iterator tied to the outer loop; consuming it later gives empty results. Convert to a list inside the loop when you need to keep it.
Common mistakes
1. Nesting comprehensions too deeply
Two levels is usually the limit. Beyond that, a named for loop is easier to read and debug.
2. Side effects inside a comprehension
[print(n) for n in numbers] # builds a useless list of None
Use a plain loop for side effects.
3. Reusing an exhausted generator
gen = (n * n for n in numbers)
list(gen) # [1, 4, 9, 16, 25]
list(gen) # []
4. groupby without sorting
Equal keys that are not adjacent produce multiple groups, which looks like data loss.
5. Mixing up sorted and list.sort
sorted() returns a new list; list.sort() sorts in place and returns None.
6. Overusing comprehensions for complex logic
A comprehension with three conditions and a nested function call is harder to review than a five-line loop.
Summary
- Comprehensions express transform-plus-filter; dict and set versions use the same shape.
- Generator expressions trade reusability for memory.
enumerate,zip(strict=True), andsorted(key=...)cover most iteration needs.itertoolsprovides infinite sequences, slicing, grouping, and combinatorics.
Next: modules, packages, and virtual environments.