You have written this shape several times already: make an empty list, loop, append. A comprehension says the same thing in one line.
prices = [4.99, 12.50, 3.25, 8.00]
# The loop you already know:
with_tax = []
for price in prices:
with_tax.append(round(price * 1.2, 2))
print(with_tax)
# The comprehension:
with_tax = [round(price * 1.2, 2) for price in prices]
print(with_tax)Read it left to right as English: the rounded price times 1.2, for each price in prices. The expression comes first because the result is what matters.
Filtering
Add if at the end to keep only some items.
numbers = range(1, 21)
evens = [n for n in numbers if n % 2 == 0]
print(evens)
squares_of_odds = [n ** 2 for n in numbers if n % 2 == 1]
print(squares_of_odds)
words = ["apple", "fig", "banana", "kiwi", "cherry"]
long_words = [w.upper() for w in words if len(w) > 4]
print(long_words)The order is always the same: what you want, for what you loop over,
if what you keep.
if in front means something different
[x if x > 0 else 0 for x in values] is a conditional expression choosing the
value, so it comes before the for. [x for x in values if x > 0] is a
filter, so it comes after. Front means choose, back means keep.
values = [3, -1, 4, -5, 9]
print([v if v > 0 else 0 for v in values]) # replaces negatives with 0
print([v for v in values if v > 0]) # drops negatives entirelyDictionary and set comprehensions
The same syntax with braces builds dictionaries and sets.
words = ["apple", "fig", "banana"]
lengths = {word: len(word) for word in words}
print(lengths)
# Invert a dictionary in one line:
capitals = {"France": "Paris", "Japan": "Tokyo"}
print({city: country for country, city in capitals.items()})
# A set comprehension - duplicates collapse:
letters = {letter for word in words for letter in word}
print(sorted(letters))That last one has two for clauses: they read in the same order you would
write them as nested loops — outer first, inner second.
Generator expressions
Swap the brackets for parentheses and nothing is built in memory; values are produced one at a time, as needed.
numbers = range(1, 1_000_001)
# A list of a million squares - all held in memory at once.
squares_list = [n ** 2 for n in numbers]
print(type(squares_list), len(squares_list))
# A generator - nothing computed yet.
squares_gen = (n ** 2 for n in numbers)
print(type(squares_gen))
# sum() consumes it one value at a time, using almost no memory.
print(sum(n ** 2 for n in range(1, 1001)))When you pass a comprehension straight into sum, max, any or all, drop
the brackets — the generator form does the same job without building a
throwaway list.
passwords = ["hunter2", "correct-horse", "12345678"]
print(any(len(p) < 8 for p in passwords)) # is any too short?
print(all(len(p) >= 7 for p in passwords)) # are all long enough?
print(sum(1 for p in passwords if p.isdigit())) # count matching itemsReadable beats clever
A comprehension should fit on one line and hold one idea. Once you are nesting
three for clauses with two conditions, a plain loop is the kinder choice — for
the next reader, and for you.
Before you write your own
1. Predict. Four comprehensions, four lines. The second is the “choose versus keep” one.
nums = [3, -2, 7, 0, -5]
print([n * 2 for n in nums if n > 0])
print([n if n > 0 else 0 for n in nums])
print({n: n * n for n in range(1, 4)})
print(sum(1 for n in nums if n < 0))Check your prediction
[6, 14]
[3, 0, 7, 0, 0]
{1: 1, 2: 4, 3: 9}
2The first keeps only the positives, so it’s shorter than nums. The second
chooses a value for every item, so it’s the same length. The last counts: one
for each item that passes the filter.
2. Modify. Rewrite this loop as a single comprehension. It should still
print [12.5, 45.0, 60.0].
expenses = [12.5, 3.0, 45.0, 8.25, 60.0]
big = []
for e in expenses:
if e > 10:
big.append(e)
print(big)Check your change
expenses = [12.5, 3.0, 45.0, 8.25, 60.0]
big = [e for e in expenses if e > 10]
print(big)[12.5, 45.0, 60.0]The three parts move across unchanged: what you append (e), what you loop
over (for e in expenses) and what you keep (if e > 10).
Clean a data file
The list holds raw lines from a form. Produce a list of clean names — stripped of whitespace, title-cased, with blank entries dropped — then a dictionary mapping each name to its length. Your output should be:
['Ada Lovelace', 'Alan Turing', 'Grace Hopper']
{'Ada Lovelace': 12, 'Alan Turing': 11, 'Grace Hopper': 12}raw = [" ada lovelace ", "", "ALAN TURING", " ", "grace hopper"]
# Your code hereHint 1: the idea
Two comprehensions. The first changes each line — strip it, then title-case it
("ada lovelace".title() is "Ada Lovelace") — and keeps only the lines that
aren’t blank. The second builds a dictionary from the clean names. A line is
blank if nothing is left after stripping.
Hint 2: a first step
Start with only the tidying: [line.strip().title() for line in raw], and print
it. You’ll see two empty strings in the result. Add a filter at the end to drop
them — and remember from Day 2 which strings count as false.
Show one solution
raw = [" ada lovelace ", "", "ALAN TURING", " ", "grace hopper"]
names = [line.strip().title() for line in raw if line.strip()]
print(names)
lengths = {name: len(name) for name in names}
print(lengths)if line.strip() uses truthiness from Day 2 — an empty or whitespace-only
string is falsy, so those lines are dropped without a length check.
Day 4 is done
Functions and comprehensions are the two habits that most separate Python that reads well from Python that merely runs.
Tomorrow, the world pushes back: errors, files, and the standard library.
What you learned
[expression for item in iterable]builds a list in one line.- A trailing
iffilters; a leadingif/elsechooses a value. {k: v for ...}and{v for ...}build dictionaries and sets.(expression for item in iterable)is a lazy generator — ideal insidesum,any,all.- Keep them to one idea; reach for a loop when they stop reading cleanly.