Comprehensions

Python · Practical · 10 min

Comprehensions replace most append-in-a-loop code and are how idiomatic Python transforms data.

A list comprehension describes a new list in terms of an existing iterable: [expression for item in iterable if condition]. It reads as "the expression, for each item, keeping those where the condition holds". It's shorter than a loop with append, and typically a bit faster.

The same syntax builds other containers:

  • dict comprehension: {name: len(name) for name in names}
  • set comprehension: {word.lower() for word in words}
  • generator expression: (x * x for x in nums) — lazy, produces items on demand; pass it straight to a function like sum(x * x for x in nums) without extra parentheses

The if at the end filters. To transform conditionally, put a conditional expression in the expression position instead: [x if x > 0 else 0 for x in nums].

Multiple for clauses nest left to right, like nested loops written on one line: [(r, c) for r in range(3) for c in range(3)]. One level of nesting is fine; beyond that, a regular loop is clearer.

Comprehensions have their own scope, so the loop variable doesn't leak into the surrounding function. Python 3.12 inlined list, dict and set comprehensions (PEP 709), making them faster without changing that visible behaviour.

Guidelines from experienced reviewers: use comprehensions to build a value, not for side effects ([print(x) for x in xs] creates a useless list); and if it doesn't fit comfortably on one or two lines, write the loop.

Example

nums = [3, -1, 4, -1, 5, -9]

squares = [n * n for n in nums]
positives = [n for n in nums if n > 0]
clamped = [n if n > 0 else 0 for n in nums]
print(positives)           # [3, 4, 5]
print(clamped)             # [3, 0, 4, 0, 5, 0]

words = ["Apple", "banana", "apple", "Cherry"]
unique = {w.lower() for w in words}
lengths = {w: len(w) for w in words}
print(sorted(unique))      # ['apple', 'banana', 'cherry']
print(lengths["banana"])   # 6

total = sum(n * n for n in nums)   # generator expression, no list built
print(total)               # 133

grid = [(r, c) for r in range(2) for c in range(3)]
print(grid)   # [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]

Remember

  • [expr for x in it if cond]: map and filter in one expression
  • Use {k: v ...} for dicts, {x ...} for sets, (x ...) for lazy generators
  • Prefer a loop for side effects or anything beyond one nesting level

Watch out for

  • Putting else after the trailing if filter — the if/else belongs in the expression part.

Check yourself

  1. What does `[x * 2 for x in range(5) if x % 2]` evaluate to?
  2. Why is `sum(x*x for x in data)` preferable to `sum([x*x for x in data])` for huge data?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/tutorial/datastructures.html#list-comprehensions