Comprehensions

The single most "Pythonic" idiom: build a new list, set, or dict from an existing iterable in one readable line — the rough equivalent of Java's stream().filter().map().collect(), but built into the language.

list compdict comp set compfilter + map generators

The pattern they replace

Constantly in Python you take one sequence and build another from it — square each number, keep the even ones, map words to their lengths. The literal way is a three-line loop:

out = []
for n in range(8):
    if n % 2 == 0:
        out.append(n * n)

A comprehension collapses that exact pattern — create an empty collection, loop, optionally filter, transform, append — into one expression:

out = [n * n  for n in range(8)  if n % 2 == 0]

It's not just shorter — it signals intent ("I'm building a list by transforming a sequence") and is usually faster than the manual loop. The trick to reading one is that the parts are in a slightly surprising order.

How to read a comprehension

Although you write the output expression first, Python evaluates it left-to-right in execution order:

[ n * n   for n in range(8)   if n % 2 == 0 ]
  1. for n in range(8) — the source: walk each item, binding it to n.
  2. if n % 2 == 0 — the optional filter: skip items that fail the test.
  3. n * n — the transform: this expression's value is what goes into the result.

So: source → filter → transform → collect. Build one yourself and watch each item flow through. Toggle the collection type to see the three siblings — list […], set {…} (de-duplicates!), and dict {k: v …}.

source = range(8) — each item is kept (✓) or dropped by the filter (✕), then transformed:

The comprehension:

…is exactly this loop:

Result:

The generator sibling: ( … )

Swap the brackets for parentheses and you get a generator expression: (n * n for n in range(8)). It looks identical but behaves very differently: it doesn't build the whole collection in memory — it produces items one at a time, on demand (lazy evaluation). That's why sum(n * n for n in range(1_000_000)) uses almost no memory, while the list version [n * n for n in range(1_000_000)] would materialise a million numbers first. Reach for a generator when you only need to iterate once (feeding sum, any, max, a for loop) and the data is large.

When to use them — and when not to

Comprehensions shine for the map/filter shape: one source, a simple condition, a simple transform. They're declarative and fast. But they're easy to abuse:

Takeaways: a comprehension is source → filter → transform → collect in one line. […] builds a list, {…} a set (de-duped) or dict ({k: v}), (…) a lazy generator. Use them for clear map/filter work; fall back to a loop when logic gets gnarly or you only want side effects.