A comprehension is a one-line expression that builds a list, set, or dictionary from another iterable. Comprehensions are one of the things people notice first about Python code — once you can read them, you’ll see them everywhere.
List comprehensions
The pattern:
[ expression for item in iterable ]
Read it as: “give me expression for each item in iterable”.
# squares of 1 to 5
squares: list[int] = [n * n for n in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
# uppercase each word
words: list[str] = ["python", "ai", "ml"]
shouted: list[str] = [w.upper() for w in words]
print(shouted) # ['PYTHON', 'AI', 'ML']
# extract a field from a list of dictionaries
people: list[dict[str, int]] = [{"age": 30}, {"age": 25}, {"age": 40}]
ages: list[int] = [p["age"] for p in people]
print(ages) # [30, 25, 40]
The equivalent loop form:
squares: list[int] = []
for n in range(1, 6):
squares.append(n * n)
The comprehension is shorter and often faster. Both versions are valid; pick whichever is clearer for the case at hand.
With a filter
Add an if to keep only certain items:
[ expression for item in iterable if condition ]
# even numbers from 1 to 20
evens: list[int] = [n for n in range(1, 21) if n % 2 == 0]
print(evens) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# words longer than 4 characters
words: list[str] = ["go", "python", "ai", "kotlin"]
long_words: list[str] = [w for w in words if len(w) > 4]
print(long_words) # ['python', 'kotlin']
The equivalent with a regular loop is several lines longer.
With if/else (in the expression)
The earlier if is a filter — items it skips don’t appear in the result.
If you want to transform items differently based on a condition, use an if/else inside the expression (a ternary):
nums: list[int] = [-2, -1, 0, 1, 2]
signs: list[str] = ["pos" if n > 0 else "zero" if n == 0 else "neg" for n in nums]
print(signs) # ['neg', 'neg', 'zero', 'pos', 'pos']
The position of the if is what matters:
# filter — keeps only positives
[n for n in nums if n > 0]
# transform — produces a label for each item
["pos" if n > 0 else "neg" for n in nums]
# both — transform, then filter
["pos" if n > 0 else "neg" for n in nums if n != 0]
Nested comprehensions
You can nest one comprehension inside another. Useful for grids and pairs:
# all pairs (i, j) for i, j in 1..3
pairs: list[tuple[int, int]] = [(i, j) for i in range(1, 4) for j in range(1, 4)]
print(pairs)
# [(1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3)]
# flatten a 2D list
matrix: list[list[int]] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat: list[int] = [n for row in matrix for n in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
The order of the for clauses is the same order you’d write nested for loops — outer first.
Limit yourself to two
forclauses. Three or more becomes unreadable. Write the loop out longhand instead.
Set comprehensions
Same syntax, with { } instead of [ ]:
words: list[str] = ["python", "ml", "python", "ai", "ml"]
unique: set[str] = {w for w in words}
print(unique) # {'python', 'ml', 'ai'}
Useful when you want to build a set in one line, often combined with a filter.
Dictionary comprehensions
Same syntax, but the expression is key: value:
squares: dict[int, int] = {n: n * n for n in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# build a dict from two lists using zip
names: list[str] = ["alice", "bob", "carol"]
ages: list[int] = [30, 25, 35]
people: dict[str, int] = {name: age for name, age in zip(names, ages)}
print(people)
# {'alice': 30, 'bob': 25, 'carol': 35}
zip() pairs items from two lists together. We’ll cover it in the next section.
When to use a comprehension
Yes — for short, single-purpose transformations:
emails: list[str] = [u.email for u in users]
adults: list[User] = [u for u in users if u.age >= 18]
seen: set[int] = {ev.user_id for ev in events}
No — when the comprehension grows long, includes side effects, or has more than two levels:
# avoid — write it out instead
result = [some_complex_expression(x) for x in items if conditional(x) and another(x)]
A simple test: if you can’t read the comprehension aloud as one sentence, it’s too long.
A note on generator expressions
Replace the brackets with parentheses and you get a generator expression — like a comprehension but lazy:
total: int = sum(n * n for n in range(1, 1_000_000))
It doesn’t build a list in memory; it produces values one at a time. We’ll cover generators properly in Section 8.
Summary of Section 6
You now know Python’s four core data structures:
- List — ordered, changeable. Default choice for “a bunch of things in order”.
- Tuple — ordered, fixed. For records and multiple return values.
- Set — unordered, unique. For membership tests and removing duplicates.
- Dictionary — key/value lookups. For structured data, configs, and JSON.
Plus comprehensions — the one-line builders you’ll use every day.
If you’re heading toward AI or ML, no other section is more important. Spend some time playing in the REPL with these structures.
What’s next
Section 7: Pythonic Functional Tools — map, filter, zip, enumerate, sorted, any, all.