Python · Intermediate
Comprehensions in Python
In short: A comprehension builds a list, dictionary or set from an iterable in a single expression: [expr for item in iterable if condition]. It replaces the common loop that starts with an empty collection and appends inside the body, and it states what the result is rather than how to build it.
What a comprehension is and why it reads better
Many loops have the same shape: start with an empty list, walk over some items, maybe skip a few, transform each one and append the result. A comprehension writes that whole pattern as one expression that reads like a description of the result: [w for w in weights if w > 10] is "the weights that are over ten". The intent is visible at a glance, and there is no temporary variable to keep track of.
The parts appear in a fixed order. First the expression that produces each element, then one or more for clauses, then an optional if that filters. The if comes last and belongs to the loop; it decides whether an item is used at all. A conditional expression inside the leading part, "free" if cell == 0 else "taken", is different: it chooses between two values for every item and must have an else.
Which brackets you use decides what you build. Square brackets make a list. Curly braces with key: value make a dictionary; curly braces with a single expression make a set. Parentheses do not make a tuple: (x for x in items) is a generator expression, which produces items lazily one at a time and is covered in the generators lesson. Pass one directly to sum(), max() or any() when you never need the whole list.
Comprehensions can nest. Two for clauses read left to right exactly as the equivalent nested loops would be written, so [cell for row in grid for cell in row] walks each row, then each cell in it. The leading expression can itself be a comprehension, which is how a grid is read from input: [[int(x) for x in input().split()] for _ in range(rows)].
The loop variable of a comprehension is local to it and does not leak into the surrounding scope, unlike the variable of a plain for statement. The reason to use a comprehension is readability rather than speed: it says what the result is, whereas a loop says how to build it.
Syntax
[expr for item in iterable] # list
[expr for item in iterable if cond] # filtered list
[a if cond else b for item in iterable] # choose a value per item
{key_expr: value_expr for item in iterable} # dict
{expr for item in iterable} # set
(expr for item in iterable) # generator, not a tuple
[expr for row in rows for cell in row] # nested loops, outer firstAny iterable works on the right of for: a list, a string, a range, a file object or a dictionary's items().
Filtering and transforming a list
Parcel weights are filtered, formatted and doubled. The loop at the end builds the same list the long way to show the two are equivalent.
weights = [2.5, 14.0, 7.4, 30.5, 0.8, 19.9]
heavy = [w for w in weights if w > 10]
print(heavy)
labels = [f"{w:.1f} kg" for w in weights]
print(labels[:3])
doubled = [w * 2 for w in weights]
print(doubled)
result = []
for w in weights:
if w > 10:
result.append(w)
print(result == heavy)Output
[14.0, 30.5, 19.9] ['2.5 kg', '14.0 kg', '7.4 kg'] [5.0, 28.0, 14.8, 61.0, 1.6, 39.8] True
The first comprehension keeps items unchanged and filters; the second transforms every item into a string and filters nothing; the third transforms numerically. Each corresponds to a loop with an if, an append, or both, and the explicit loop at the end produces an equal list. Every comprehension keeps the order of the source.
Dictionary and set comprehensions
A price list is turned into a tax-inclusive dictionary, a set of cheap items, an inverted lookup and a dictionary of name lengths.
prices = {"loaf": 3.2, "roll": 0.6, "bagel": 1.1, "cake": 12.0}
with_tax = {name: round(p * 1.1, 2) for name, p in prices.items()}
print(with_tax)
cheap = {name for name, p in prices.items() if p < 2}
print(sorted(cheap))
by_price = {p: name for name, p in prices.items()}
print(by_price[0.6])
lengths = {name: len(name) for name in prices}
print(lengths)Output
{'loaf': 3.52, 'roll': 0.66, 'bagel': 1.21, 'cake': 13.2}
['bagel', 'roll']
roll
{'loaf': 4, 'roll': 4, 'bagel': 5, 'cake': 4}Iterating over prices.items() and unpacking name, p gives both halves of each entry. The dictionary comprehension keeps the keys and computes new values; the set comprehension keeps only names and drops the values, so it is printed through sorted() for a stable order. Swapping the roles of key and value builds a reverse lookup, which only works because the prices are distinct. Iterating over a dictionary directly, as in lengths, yields its keys.
Nested comprehensions on a grid
A seating grid is read from input, flattened, labelled with a conditional expression and totalled by column.
rows = int(input())
grid = [[int(x) for x in input().split()] for _ in range(rows)]
print(grid)
flat = [cell for row in grid for cell in row]
print(flat)
status = ["free" if cell == 0 else "taken" for cell in flat]
print(status.count("taken"))
column_totals = [sum(row[i] for row in grid) for i in range(len(grid[0]))]
print(column_totals)Input given to the program: 3 ↵ 0 1 1 ↵ 1 0 0 ↵ 0 0 1
Output
[[0, 1, 1], [1, 0, 0], [0, 0, 1]] [0, 1, 1, 1, 0, 0, 0, 0, 1] 4 [1, 1, 2]
The grid is a comprehension whose element expression is another comprehension: the outer one runs once per row, the inner one converts each token on that line. flat uses two for clauses in the same comprehension, outer row first, then cell. status shows a conditional expression choosing one of two labels for every cell. The column totals pass a generator expression to sum(), so no intermediate list is built for each column.
Which brackets build what
| Syntax | Builds | Result |
|---|---|---|
| [n * n for n in range(3)] | list | [0, 1, 4] |
| {n: n * n for n in range(3)} | dict | {0: 0, 1: 1, 2: 4} |
| {n % 2 for n in range(3)} | set | {0, 1} |
| (n * n for n in range(3)) | generator | a generator object; wrap in list() to see the values |
Common mistakes
Putting the filter before the loop
Why it goes wrong:
[x if x > 0 for x in nums]is a syntax error. A filter is anifafter theforclause; anifbefore theforis a conditional expression and needs anelse.Fix: Write
[x for x in nums if x > 0]to filter, or[x if x > 0 else 0 for x in nums]to replace negative values.Using a comprehension only for its side effects
Why it goes wrong:
[print(x) for x in items]builds a list ofNonevalues that is thrown away. Readers expect a comprehension to produce a value, so this hides the intent.Fix: Use a plain
forloop when the body does something rather than computes something.Nesting until nobody can read it
Why it goes wrong: A comprehension with three
forclauses, two filters and a conditional expression fits on one line but takes minutes to decode; the compactness stops paying for itself.Fix: Split it into named steps or use a loop. A comprehension should be readable aloud in one breath.
Expecting parentheses to give a tuple
Why it goes wrong:
(x * 2 for x in nums)is a generator expression. It cannot be indexed, has no length, and can be consumed only once.Fix: Wrap it:
tuple(x * 2 for x in nums).Python · fixpairs = tuple((n, n * n) for n in range(3)) print(pairs) # ((0, 0), (1, 1), (2, 4))
Where you use this
Data clean-up is where comprehensions shine. Lines read from a file arrive with trailing newlines and blank lines mixed in; [line.strip() for line in lines if line.strip()] gives you clean, non-empty lines in one step. A list of prices as strings becomes numbers with [float(p) for p in raw], a list of records becomes a lookup table with {r["id"]: r for r in records}, and a column is pulled out of a grid with [row[2] for row in grid].
Aggregation pairs with generator expressions. sum(order["total"] for order in orders if order["paid"]) adds up paid orders without building an intermediate list, and any(len(name) > 30 for name in names) stops at the first long name it finds.
clean = [line.strip() for line in lines if line.strip()]
by_id = {r["id"]: r for r in records}
total = sum(o["total"] for o in orders if o["paid"])Key points
- A comprehension is an expression that builds a collection: element expression, then
forclauses, then an optionaliffilter. - Square brackets make a list,
{k: v ...}a dictionary,{v ...}a set, and parentheses a generator. - A conditional expression in the element position needs an
else; a filter after thefordoes not. - Nested
forclauses read in the same order as nested loops, outer loop first. - The loop variable does not leak out of the comprehension.
- Use a plain loop when the body performs actions or when the comprehension stops being readable.
Try it yourself
The comprehension currently collects the squares of every number from 1 to n. Add a filter so it keeps only the squares of the odd numbers, then print the list.
n = int(input())
squares = [k * k for k in range(1, n + 1)]
print(squares)7[1, 9, 25, 49]n = int(input())
squares = [k * k for k in range(1, n + 1) if k % 2 == 1]
print(squares)Practise this
Exercises for this lesson are in the Python practice set.
Related lessons
- Lists in PythonHow Python lists store ordered, changeable collections: creating them, indexing and slicing, adding and removing items, sorting, copying and looping over them.9 min
- Loops in Python: for and whileHow a for loop walks through a sequence and a while loop repeats until a condition fails, with range(), enumerate(), break and continue explained by example.9 min
- Dictionaries in PythonHow Python dictionaries map keys to values: creating and updating entries, safe lookup with get, iterating items in insertion order, counting and grouping.9 min
- Sets in PythonHow Python sets store unique, unordered items: removing duplicates, fast membership tests, union, intersection and difference, and why set order is unreliable.8 min
Frequently asked questions
Is a list comprehension faster than a for loop?
Usually a little, because the append and the loop bookkeeping are handled inside the interpreter rather than as separate bytecode for each item, and Python 3.12 inlines comprehensions to remove one more function call. The difference is small compared with the readability gain, so choose a comprehension because it states the result clearly, not for speed.
Can a list comprehension have an else?
Only as part of a conditional expression in the element position: [x if x > 0 else 0 for x in nums]. The filtering if after the for clause cannot have an else, because it decides whether to include an item at all rather than which value to produce.
What is the difference between a list comprehension and a generator expression?
A list comprehension, written with square brackets, builds the whole list in memory immediately; you can index it, take its length and loop over it repeatedly. A generator expression, written with parentheses, produces one item at a time on demand, uses almost no memory, and can be consumed only once. Pass a generator expression to sum, max, any or all when you need only the aggregate.
How this page was checked. Every program on it was run with CPython 3.11 at build time; runs in your browser on CPython 3.14 (Pyodide) by the publishing checks, and the output shown is what it printed. The Run buttons execute the same code on your device; nothing you type is sent anywhere.