Comprehensions
Filter and transform a whole collection in one readable line.
Why it matters
Most data work is "take this collection and give me a cleaned, filtered or changed version of it". Comprehensions say that in one line. AI code is full of them: tidying documents before they're embedded, picking out the messages with a given role, building a lookup table of scores. Even when an assistant writes the code, you need to read these lines fluently to know what they do.
What you'll be able to do
- Build a new list from an existing one with a list comprehension
- Keep only some items with an if clause
- Loop over a dictionary's keys, values, or both with .items()
- Build a dictionary with a dict comprehension
- Decide when a plain loop is clearer than a comprehension
What it covers
- The loop you keep writing
- Filtering with if
- Looping over a dictionary
- When a loop is better
- Worked example: cleaning a lead list
- Where the if goes
- Saving the result
The loop you keep writing
You've written this pattern already: start an empty list, loop over another list, append a changed version of each item.
emails = ["Ana@Shop.com", "BEN@mail.com", "cara@site.io"] clean = [] for e in emails: clean.append(e.lower()) print(clean)
Picture asking a helper to lowercase a list of emails. You wouldn't say "take the first one, lowercase it, write it down, now take the second one". You'd say "lowercase every email on this list". A list comprehension is that one-sentence instruction:
emails = ["Ana@Shop.com", "BEN@mail.com", "cara@site.io"] clean = [e.lower() for e in emails] print(clean)
Read it left to right as "e.lower(), for each e in emails". The general shape is:
[expression for item in collection]
The square brackets say you're building a list. The for ... in ... part is the same loop you already know. The expression at the front is what goes into the new list for each item. The original list is left unchanged.
prices = [28, 64, 18, 22, 26] with_tax = [round(p * 1.2, 2) for p in prices] print(with_tax) print(prices)
The loop builds active one item at a time, checking each one. Line 7 does the same filtering in a single step.
Filtering with if
Add an if at the end to keep only the items that pass a test:
[expression for item in collection if condition]
campaigns = ["Spring Sale - Search", "Retargeting - Display", "Competitor Terms - Search", "Newsletter Promo - Email"] search = [name for name in campaigns if name.endswith("Search")] print(search) spend = [3120.5, 1890, 6400, 120, 0, 2390.75] big = [s for s in spend if s >= 2000] print(big)
Sometimes you don't want to drop items. You want to choose between two values for each one. That uses Python's one-line if/else: value_if_true if condition else value_if_false. It goes at the front, as the expression:
spend = [3120.5, 1890, 6400, 120] labels = ["high" if s > 2000 else "low" for s in spend] print(labels)
So: an if at the end filters (fewer items come out), and an if/else at the front chooses a value (the same number of items comes out).
print([n * 10 for n in [1, 2, 3] if n != 2])
Show the answer
[10, 30]
The if clause drops 2. The remaining items, 1 and 3, are each multiplied by 10.
nums = [3, 8, 1, 9]. Which comprehension gives back exactly 4 items?Show the answer
["big" if n > 5 else "small" for n in nums]
An if/else at the front chooses a value for every item, so 4 go in and 4 come out. An if at the end filters, so the others give back fewer items.
Looping over a dictionary
Before building dictionaries with comprehensions, you need to loop over one. A plain for loop over a dictionary gives you its keys:
budgets = {"search": 5000, "display": 2000, "video": 8000, "email": 500}
for channel in budgets:
print(channel, budgets[channel])
Three methods let you choose what you loop over:
budgets.keys()gives the keys (the same as looping over the dictionary itself)budgets.values()gives the valuesbudgets.items()gives each key and value together, as a pair
With .items(), put two names after for and Python unpacks each pair into them. This is the form you'll use most:
budgets = {"search": 5000, "display": 2000, "video": 8000, "email": 500}
for channel, amount in budgets.items():
print(f"{channel}: {amount}")
print(list(budgets.keys()))
print(sum(budgets.values()))
Dictionary comprehensions
Swap the square brackets for curly braces, and give a key: value pair. Now the comprehension builds a dictionary instead of a list:
{key: value for item in collection}
products = ["tee", "hoodie", "mug"] name_lengths = {p: len(p) for p in products} print(name_lengths)
To transform an existing dictionary, put the same for channel, amount in budgets.items() loop inside the braces. Here's the loop version and the comprehension version side by side:
budgets = {"search": 5000, "display": 2000, "video": 8000, "email": 500}
raised_loop = {}
for channel, amount in budgets.items():
raised_loop[channel] = round(amount * 1.1)
raised = {channel: round(amount * 1.1) for channel, amount in budgets.items()}
large = {channel: amount for channel, amount in budgets.items() if amount >= 2000}
print(raised_loop == raised)
print(raised)
print(large)
zip is handy here. It walks two lists side by side, pairing the first items, then the second items, and so on:
subjects = ["Last chance", "New arrivals", "Weekly picks"] open_rates = [0.26, 0.19, 0.14] by_subject = {s: r for s, r in zip(subjects, open_rates)} print(by_subject)
Comprehensions with sum, max and len
A comprehension often feeds straight into a function that summarizes it:
campaigns = [
{"name": "Spring Sale", "spend": 3120.5, "revenue": 15840},
{"name": "Retargeting", "spend": 1890, "revenue": 5236},
{"name": "Brand Video", "spend": 6400, "revenue": 2870},
]
total_spend = sum([c["spend"] for c in campaigns])
profitable = [c["name"] for c in campaigns if c["revenue"] > c["spend"]]
best_revenue = max([c["revenue"] for c in campaigns])
print(total_spend)
print(profitable)
print(best_revenue)
You'll also see this written without the square brackets, as sum(c["spend"] for c in campaigns). That's a generator expression. It hands over the values one at a time instead of building the whole list first. The result is the same.
stock = {"tee": 1, "mug": 5, "cap": 3}
print({k: v * 2 for k, v in stock.items() if v > 2})Show the answer
{'mug': 10, 'cap': 6}
The if drops tee, because 1 isn't more than 2. mug doubles to 10 and cap doubles to 6, and the curly braces with k: v make the result a dictionary: {'mug': 10, 'cap': 6}.
When a loop is better
Comprehensions are for building one collection from another. Write a regular loop when:
- each item needs several steps, or nested if/else logic
- you're doing something for its effect, like printing or saving, rather than building a list
- you need to update several things at once (two lists, a running total and a count)
- the one-liner no longer fits comfortably on one line
Python allows comprehensions inside comprehensions, but they get hard to read fast. If you have to stop and decode a line, so will the next person. Clear beats clever.
For marketers: a comprehension is like a filtered, calculated column in a spreadsheet: one rule, applied to every row, producing a new column.
Worked example: cleaning a lead list
A form export has messy emails: stray spaces, mixed capitals, blanks, a junk entry and a duplicate. Produce a clean, deduplicated list.
raw_emails = [" Ana@Shop.com", "ben@mail.com ", "", "CARA@site.io", "not-an-email", "ben@mail.com"] cleaned = [e.strip().lower() for e in raw_emails] valid = [e for e in cleaned if "@" in e] unique = sorted(set(valid)) print(len(raw_emails), "raw ->", len(unique), "usable") print(unique)
Each line does one job. The first comprehension transforms every item (strip the spaces, lowercase). The second filters, keeping only strings with an @. Then set(valid) removes duplicates, because a set can't hold the same value twice, and sorted turns the result back into an alphabetical list. Three short, readable passes beat one long loop that tries to do everything at once.
Where the if goes
A comprehension can hold an if in two different places, and they mean different things.
prices = [5, 20, 50] print([p for p in prices if p > 10]) # keep some items print(["big" if p > 10 else "small" for p in prices]) # choose a value for every item
An if at the end filters: fewer items come out. An if/else at the front chooses: every item comes out, as one of two values. Python won't accept else at the end, so if you get a SyntaxError, check this first.
When to stop using one
Comprehensions build a new list. They don't change the original, so you still need to save the result. And they're for making a value, not for doing things. [print(p) for p in prices] works, but it builds a list of Nones that nobody wants. Use a plain loop to print or to save to a file.
Nesting is where they turn unreadable. If you need two levels of loops and a condition, a normal for loop will be clearer to the next person, who may well be you in six months. Short code isn't the goal. Code you can read at a glance is.
Saving the result
A comprehension leaves the original list untouched. To keep the result, assign it, even to the same name: prices = [p * 2 for p in prices].
Practice what you just read
- 13 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 6 review cards for spaced repetition
Try a quiz question
spend = [3000, 500, 8000]. Which comprehension gives ['high', 'low', 'high']?