The grammar: transform, loop, filter — left, middle, right. Every example below is that sentence rearranged.
nums = [1, 2, 3, 4, 5]
[n * n for n in nums] # transform → [1,4,9,16,25]
[n for n in nums if n % 2 == 0] # filter → [2,4]
[n*n for n in nums if n > 2] # both → [9,16,25]
["even" if n%2==0 else "odd" for n in nums] # if-else → LEFT side
The if-else position trip-up: filtering if goes on the right (drops items); choosing if-else goes on the left (keeps every item, picks its value). Mixing these up is the #1 comprehension syntax error.
# clean a list of strings
[s.strip().lower() for s in raw if s.strip()]
# pull one field out of a list of dicts (API responses!)
[u["email"] for u in users if u["active"]]
# parse numbers from a CSV row
[float(x) for x in line.split(",")]
# filenames matching a pattern
[f for f in os.listdir(".") if f.endswith(".csv")]
{w: len(w) for w in words} # dict: {"hi": 2, "hello": 5}
{u["id"]: u for u in users} # index a list by id — daily move
{n % 3 for n in nums} # set: unique values only
{v: k for k, v in d.items()} # invert a dict
matrix = [[1, 2], [3, 4], [5]]
[x for row in matrix for x in row] # → [1,2,3,4,5]
# read the fors left-to-right, same order as nested loops.
# this is also the ceiling — one level. deeper = write a loop.
[print(x) for x in items]) — that's a loop wearing a costume, and it allocates a pointless list of None. And when a comprehension needs two ifs or nested conditions, future-you wants a loop. Clever fits on one line; maintainable fits in one glance.
sum(), max(), any()? Drop the brackets: sum(n*n for n in nums) — a generator expression. Same syntax, no intermediate list, matters when the data is big.