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 for clauses. 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 Toolsmap, filter, zip, enumerate, sorted, any, all.

Toggle theme (T)