Skip to content

Comprehensions

3 min read

You've written the collect plan several times now: an empty list, a loop, an append. It's so common that Python has a one-line way to write it. By the end of this lesson you'll build lists, dicts, and sets in a single readable line, and know when a plain loop is still the better choice.

The collect loop, in one line

Here's the collect plan from lesson 4.2, adding a 5 birr delivery fee to every price:

prices = [250, 40, 1200]
with_fee = []
for price in prices:
    with_fee.append(price + 5)
print(with_fee)

In the editor, press Escape then Tab to move on.

And here's the same thing as a :

prices = [250, 40, 1200]
with_fee = [price + 5 for price in prices]
print(with_fee)

In the editor, press Escape then Tab to move on.

Read it aloud: "price + 5, for each price in prices". The parts are the same as the loop's, just rearranged: what goes into the new list comes first, then the for line. The square brackets say the result is a list. Change it so each price doubles.

After a for loop, its name still refers to the last item. A comprehension's name doesn't outlive it:

Keeping only some items

Add if at the end, and only the items that pass the test go in. This is the filter plan: collect only what passes. Predict this one.

What will this print?

Decide before you look. Guessing wrong is how this sticks.

scores = [45, 82, 67, 91, 38]
passed = [s for s in scores if s >= 50]
print(passed)
Pick the output

Python prints

[82, 67, 91]

The if decides which scores go in; each one that passes goes in as itself, because the expression before for is just s. You get a list of the passing scores, not True/False values, and not a count.

The same as a loop would be:

passed = []
for s in scores:
    if s >= 50:
        passed.append(s)

Quick check

Which comprehension gives the even numbers from 1 to 10?

Dicts and sets too

With curly brackets and key: value before the for, you get a dict. With curly brackets and a single value, a set:

names = ["Hana", "Abel", "Selam"]
lengths = {name: len(name) for name in names}
print(lengths)
sizes = {len(name) for name in names}
print(sorted(sizes))

In the editor, press Escape then Tab to move on.

lengths pairs each name with its number of letters. sizes keeps each length once, because it's a set; that's why it's printed sorted (lesson 4.5).

When to use a loop instead

A comprehension is for one clear idea: make this from that, maybe with one test. When it stops fitting on one readable line, a loop is better. This works, but it takes a while to read:

report = [f"{name}: {score}" for name, score in zip(names, scores) if score >= 50 and name != "Abel"]

Written as a loop, each step gets its own line, and there's room for more later, like printing or counting along the way:

report = []
for name, score in zip(names, scores):
    if score >= 50 and name != "Abel":
        report.append(f"{name}: {score}")

Also use a loop when you want something to happen, like printing, rather than a new list. A comprehension's job is to build a collection.

Exercise

VAT on the big items

Add 15% VAT (multiply by 115, divide by 100) to each price, but only for items over 100 birr. Build the new list with one comprehension and print it.

Output
[287.5, 1380.0]

Your code

prices = [80, 250, 1200, 45]
# One comprehension: price with VAT, only for prices over 100

In the editor, press Escape then Tab to move on.

Show a solution

This is one way to solve it, not the only one. If yours prints the same thing, it works.

prices = [80, 250, 1200, 45]
with_vat = [price * 115 / 100 for price in prices if price > 100]
print(with_vat)

Key takeaways

  • [expression for name in items] builds a list in one line: the collect plan.
  • Add if test at the end to keep only some items: the filter plan.
  • The comprehension's loop name doesn't exist after it.
  • {k: v for ...} builds a dict, and {v for ...} builds a set.
  • If it doesn't fit on one readable line, or you want to print rather than build, use a loop.