List and dictionary comprehensions
Promised back in the for loops lesson: a shorter way to write the
build-a-new-list pattern. Comprehensions are one of the things that make Python
code look like Python, and they are also one of the easiest features to overuse.
The pattern they replace
You have written this shape repeatedly:
numbers = [1, 2, 3, 4, 5]
doubled = []
for number in numbers:
doubled.append(number * 2)
print(doubled)
[2, 4, 6, 8, 10]
Four lines, three of which are bookkeeping. As a comprehension:
doubled = [number * 2 for number in numbers]
Same result. The structure is:
[ what to keep for each item in the source ]
Read it right to left at first: "for each number in numbers, keep number times two." After a week you will read it left to right without noticing.
Adding a condition
evens = []
for number in numbers:
if number % 2 == 0:
evens.append(number)
becomes:
evens = [number for number in numbers if number % 2 == 0]
The if goes at the end and filters: items failing it never reach the list.
Both together:
doubled_evens = [n * 2 for n in numbers if n % 2 == 0]
print(doubled_evens)
[4, 8]
Filter first, then transform what survives.
if/else is different, and goes elsewhere
This is the part people get wrong:
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(labels)
['odd', 'even', 'odd', 'even', 'odd']
Note the position. An if at the end filters. An if/else at the
front chooses a value — it is the conditional expression from module 3, not a
filter.
The reason is simple: a filter can drop an item, so it belongs with the
iteration. An if/else always produces something, so it belongs where the
value is described.
Getting these confused gives a SyntaxError, which is at least a quick failure:
[n for n in numbers if n % 2 == 0 else 0] # SyntaxError
If you want every item, with a substitute for the ones that fail, you want the front form:
[n if n % 2 == 0 else 0 for n in numbers] # [0, 2, 0, 4, 0]
Working on strings
words = ["python", "is", "great"]
lengths = [len(w) for w in words] # [6, 2, 5]
upper = [w.upper() for w in words] # ['PYTHON', 'IS', 'GREAT']
long_words = [w for w in words if len(w) > 3] # ['python', 'great']
A common cleaning job, stripping and dropping blanks in one line:
raw = [" apple ", "", " banana", " "]
cleaned = [line.strip() for line in raw if line.strip()]
print(cleaned)
['apple', 'banana']
line.strip() runs twice, which is slightly wasteful and perfectly normal here.
Readability wins for a list this size.
Dictionary comprehensions
Same idea, curly brackets and a colon:
words = ["python", "is", "great"]
lengths = {word: len(word) for word in words}
print(lengths)
{'python': 6, 'is': 2, 'great': 5}
Inverting a dictionary:
prices = {"tea": 40, "coffee": 120}
by_price = {value: key for key, value in prices.items()}
print(by_price)
{40: 'tea', 120: 'coffee'}
Careful — if two items share a price, one silently overwrites the other. Values are not guaranteed unique, so inverting is only safe when you know they are.
Filtering works the same way:
expensive = {k: v for k, v in prices.items() if v > 50}
Set comprehensions
Curly brackets, no colon:
text = "hello world"
unique_letters = {letter for letter in text if letter != " "}
print(unique_letters)
Unordered and deduplicated, as sets are.
Nested comprehensions
They work, and this is where to start being careful.
Flattening:
grid = [[1, 2], [3, 4], [5, 6]]
flat = [n for row in grid for n in row]
print(flat)
[1, 2, 3, 4, 5, 6]
The loops read in the same order you would write them normally — outer first, then inner. That surprises people who expect the reverse.
Building the grid from the list-methods lesson, where [[0] * 3] * 3 failed:
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 1
print(grid)
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
Each pass creates a new inner list, which is exactly what multiplication did not do. This is the correct way to build a grid, and worth memorising.
When not to use one
Comprehensions are a tool for one job: building a collection from another collection. Push them further and they stop being readable.
Do not use one for side effects:
[print(n) for n in numbers] # works, and is wrong
That builds a list of None values and throws it away. If you are not keeping
the result, write a normal loop.
Do not nest deeply:
result = [f(x, y) for x in xs if p(x) for y in ys if q(x, y)]
Technically valid. Nobody can read it, including you next month. Two loops and
an if are fine.
Do not force complex logic in. If the body needs several steps, or a try,
or a comment to explain it, a normal loop is the better answer. Comprehensions
should make code shorter and clearer. When they only make it shorter, they are
costing you.
A reasonable rule: if it does not fit comfortably on one line, write the loop.
A word on generators
Swap the brackets for round ones and you get a generator:
squares = (n ** 2 for n in range(1000000))
That does not build a million items. It produces them one at a time as they are asked for, which matters when the collection is large or you only need to walk it once:
total = sum(n ** 2 for n in range(1000000))
No million-item list is ever created. When you are passing straight into
sum(), max(), any() or all(), drop the square brackets and save the
memory.
Practice
-
Build a list of the squares of 1 to 10, first with a loop, then with a comprehension.
-
From
range(1, 51), make a list of numbers divisible by 3 but not 5. -
Given
names = ["priya", "ARJUN", "Sneha"], produce them all properly capitalised. -
Given
["3", "x", "7", "", "12"], build a list of integers, skipping anything that is not a number. -
Label each number in
range(1, 11)as"even"or"odd"using anif/elsecomprehension. Then explain why theifsits at the front. -
Build
{word: len(word)}for a sentence's words, then filter it to words longer than four letters. -
Flatten
[[1, 2], [3, 4], [5, 6]]. Then build a 4 x 4 grid of zeros correctly and prove that changing one cell does not change others. -
Take this and rewrite it as a comprehension, then decide which version you would rather read:
result = [] for student in students: if student["marks"]: result.append(sum(student["marks"]) / len(student["marks"]))
Next: choosing between all four structures, with worked examples.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship