PY-01-013-P · Primer — Comprehensions
The idea in one sentence
A comprehension is a one-line way to build a new list, dict, or set by describing it — "take each item, do this to it, maybe only if that's true" — instead of writing an empty container and an explicit for loop that .append()s to it.
Why it exists
Over and over in real code you do the same little dance: make an empty list, loop over some existing sequence, transform each item (or keep only the ones you want), and stash the result. Written the long way that's four lines of boilerplate whose intent — "the squares of these numbers," "the emails that bounced" — gets buried under the mechanics of result = [], for, and .append. Comprehensions exist to put the intent front and center: you say what the new collection should contain in a single readable expression, and Python handles the empty-container-and-loop plumbing for you. Because the whole thing is one expression, it also reads left-to-right like a sentence — "give me n*n for each n in nums" — and it's usually a touch faster than the hand-written loop because the appending happens in optimized C rather than in your Python code. And the same syntax works for three of Python's core collections just by swapping the brackets, so once you learn it for lists you get dicts and sets almost for free.
How it works
A list comprehension has the shape [expression for item in iterable]: the part before for is what each element becomes, and the for clause names the source and the loop variable. Add an optional if condition at the end to filter — only items that pass are included. Swap the square brackets for curly braces with a key: value pair and you get a dict comprehension; use curly braces with a single expression and you get a set comprehension (which, like any set, drops duplicates and stays unordered). All three are just this one pattern wearing different brackets. Here is each in turn, with the output shown in comments:
nums = [1, 2, 3, 4, 5, 6]
squares = [n * n for n in nums] # transform every item
print(squares) # [1, 4, 9, 16, 25, 36]
evens_squared = [n * n for n in nums if n % 2 == 0] # filter with `if`
print(evens_squared) # [4, 16, 36]
words = ["hi", "there", "world"]
lengths = {w: len(w) for w in words} # dict comprehension {key: value}
print(lengths) # {'hi': 2, 'there': 5, 'world': 5}
first_letters = {w[0] for w in words} # set comprehension (unique, unordered)
print(first_letters) # {'h', 't', 'w'} (order may vary)
The if at the end decides whether an item is kept at all; the expression at the front decides what a kept item turns into.
The one gotcha
Just because you can cram a comprehension into one line doesn't mean you should — nested loops and stacked conditions can turn it into an unreadable puzzle. Comprehensions read right-to-left when you nest a second for, which is exactly backwards from how a normal loop reads top-to-bottom, so a "clever" nested one-liner often costs the next reader (including future you) more time than it saved. Compare these two ways to flatten a list of rows:
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [x for row in matrix for x in row] # works, but reads back-to-front
print(flat) # [1, 2, 3, 4, 5, 6]
# The plain loop says the same thing in the order it happens:
flat = []
for row in matrix:
for x in row:
flat.append(x)
print(flat) # [1, 2, 3, 4, 5, 6]
The rule of thumb: a comprehension is a win when it fits comfortably on one line with at most one for and maybe one if. The moment you'd need two fors, a long if, or an if/else you have to squint at, prefer the ordinary loop — clarity beats compression.
See it — how Python builds a comprehension
flowchart TD
A["Start with an<br/>empty result"] --> B{"More items<br/>in the iterable?"}
B -->|"No"| G["Return the finished<br/>list / dict / set"]
B -->|"Yes"| C["Take the next item"]
C --> D{"Is there an `if`,<br/>and does it pass?"}
D -->|"No: skip it"| B
D -->|"Yes / no filter"| E["Evaluate the<br/>front expression"]
E --> F["Add result to<br/>the collection"]
F --> B
The one-line takeaway
Reach for a comprehension — [expr for x in xs if cond], or {k: v ...} / {expr ...} for dicts and sets — to build a collection in one clear line, but drop back to a plain loop the moment nesting or conditions hurt readability.
Where you'll meet it
Comprehensions are everywhere in idiomatic Python, and spotting where they fit is a hallmark of fluency. You'll transform data constantly — [row.strip() for row in lines] to clean file input, [float(x) for x in fields] to parse numbers, [u.name for u in users] to pull one attribute out of a list of objects. You'll filter with the trailing if — [e for e in emails if "@" in e], [n for n in nums if n > 0] — often replacing a loop that only existed to skip a few items. Dict comprehensions shine when you're reshaping data: {u.id: u for u in users} to index a list by id, or {k: v for k, v in pairs if v is not None} to drop empty entries. Set comprehensions give you unique derived values in a breath — {w.lower() for w in words} for a case-insensitive vocabulary. And the day you catch yourself writing result = [], a for, and an .append() that does nothing but transform-and-collect, you'll feel the itch to fold it into one line — that instinct, tempered by knowing when not to, is exactly the judgment this primer is training.
Named concepts: list comprehensions, conditions and filtering, dict and set comprehensions, readability vs nesting
