The shape
Read it in the order it executes: source, filter, expression. It is not shorthand for its own sake — a comprehension says "I am building a new list from this one", which a for loop with an append only implies.
squares = []
for n in numbers:
if n > 0:
squares.append(n * n)
squares = [n * n for n in numbers if n > 0]
# ^^^^^ what to keep
# ^^^^^^^^^^^^^^ where it comes from
# ^^^^^^^^^^ which ones
All four kinds
The generator version is the one worth knowing: it holds no list, produces values as they are asked for, and can run over something that does not fit in memory. When a comprehension is the only argument to a function, the extra parentheses are not needed.
[n * n for n in nums] # list
{n * n for n in nums} # set — duplicates collapse
{name: len(name) for name in names} # dict
(n * n for n in nums) # generator — builds NOTHING yet
sum(n * n for n in nums) # the parentheses are implied here
any(w.isupper() for w in words) # stops at the first True
Nesting reads outside-in
The clauses come in the same order as the nested for statements would. It is the one part of the syntax that people get backwards — write the loop first if you are unsure, then collapse it.
flat = [cell for row in grid for cell in row]
# for row in grid:
# for cell in row:
# flat.append(cell)
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
# a nested LIST, rather than a flattened one, is a comprehension inside a comprehension
transposed = [[row[i] for row in grid] for i in range(len(grid[0]))]
The filter goes at the end; the conditional value goes at the front
These two look alike and do different things. if after the for decides whether the item is included; if/else before the for is a conditional expression deciding what the item becomes.
[n for n in nums if n > 0] # filter: keep some
[n if n > 0 else 0 for n in nums] # map: change some, keep all
[n for n in nums if n > 0 if n % 2 == 0] # two filters, and-ed
When not to
Since Python 3 a comprehension has its own scope, so the loop variable does not leak — [x for x in items] leaves no x behind, unlike the for loop it replaces.
- When it does not fit on a line and a half. Two
fors and twoifs is a loop that wants to be a loop. - When you are not building anything. A comprehension whose result you throw away is a loop with the wrong punctuation — and it builds a whole list to discard it.
- When the body needs a statement.
try,breakandcontinuecannot go in one; needing them is the signal. - When you are folding.
sum,min,max,any,all,functools.reducealready exist.