WasiLearn Code
Python · Data structures

List comprehensions

Building a new list from an old one is so common that Python has a dedicated syntax for it. Once it clicks, three lines become one and the one reads better.

prices = [250, 120, 80]

with_tax = []
for price in prices:
    with_tax.append(round(price * 1.15))

print(with_tax)
print([round(price * 1.15) for price in prices])
output
[288, 138, 92]
[288, 138, 92]

Same answer. The comprehension says the whole intention on one line: the taxed price, for every price.

The shape

[ expression for item in iterable ]

Read it out loud in that order — "round(price * 1.15), for price in prices" — and it is an English sentence. The for part is identical to a normal loop header; the expression at the front is what goes into the new list.

names = ["ayesha", "bilal", "sana"]

print([name.title() for name in names])
print([len(name) for name in names])
print([n * n for n in range(1, 6)])
output
['Ayesha', 'Bilal', 'Sana']
[6, 5, 4]
[1, 4, 9, 16, 25]

It always produces a new list. The original is untouched, which is usually what you want and is worth knowing when you expected otherwise.

Adding a condition

Put an if on the end and only the items passing it are included.

marks = [72, 45, 88, 30, 91]

print([mark for mark in marks if mark >= 50])
print([mark for mark in marks if mark % 2 == 0])
output
[72, 88, 91]
[72, 88, 30]

The expression at the front can still transform:

marks = [72, 45, 88, 30, 91]

print([f"{mark}%" for mark in marks if mark >= 50])
output
['72%', '88%', '91%']

Filter and transform in one line, and the order reads correctly: what to keep is at the end, what to produce is at the front.

The other if, and why it goes somewhere else

There is a second place an if can appear, and mixing the two up is the commonest comprehension bug.

marks = [72, 45, 88]

print([mark for mark in marks if mark >= 50])
print(["pass" if mark >= 50 else "fail" for mark in marks])
output
[72, 88]
['pass', 'fail', 'pass']
  • if at the end is a filter. It decides whether an item is included, so the result can be shorter than the input.
  • if ... else at the front is a ternary — part of the expression. It decides what value to produce, so the result is always the same length.

The giveaway is else. A filter cannot have one, because there is nothing to put in the list when the test fails. If you have written else, it belongs at the front.

Nested loops

Two for clauses read in the same order as the loops they replace: outer first, inner second.

grid = [[1, 2], [3, 4], [5, 6]]

print([value for row in grid for value in row])
output
[1, 2, 3, 4, 5, 6]

That order feels backwards to most people, because the expression uses value, which comes from the second clause. The rule that fixes it: write the nested loop first, then lift the lines out in order.

for row in grid:          # first for
    for value in row:     # second for
        flat.append(value)  # the expression

Flattening is the one nested comprehension worth using. Beyond that, the readability drops off a cliff, and a real loop is better.

Dictionary and set comprehensions

The same syntax with different brackets builds the other collections.

names = ["Ayesha", "Bilal"]

print({name: len(name) for name in names})
print(sorted({len(name) for name in names}))
output
{'Ayesha': 6, 'Bilal': 5}
[5, 6]

A dict comprehension needs key: value in front. A set comprehension looks like a list comprehension in curly brackets, and deduplicates for free.

Note the sorted() around the set — a set has no order, so printing one directly is not reproducible, as the sets lesson explains.

Inverting a dictionary is the classic one-liner:

stock = {"chai": 12, "coffee": 4}

print({count: item for item, count in stock.items()})
print({item: count for item, count in stock.items() if count > 5})
output
{12: 'chai', 4: 'coffee'}
{'chai': 12}

Curly brackets do not give you a tuple

squares = (n * n for n in range(5))

print(type(squares))
print(list(squares))
output
<class 'generator'>
[0, 1, 4, 9, 16]

Round brackets produce a generator, not a tuple. A generator computes each value as it is asked for rather than building the whole list in memory, which matters when the sequence is huge or infinite.

The catch is that it is consumed once:

squares = (n * n for n in range(5))

print(list(squares))
print(list(squares))
output
[0, 1, 4, 9, 16]
[]

The second call gets nothing, because the first one walked to the end. If you need the values twice, use a list.

Where generators shine is feeding a function that only needs one pass:

marks = [72, 45, 88]

print(sum(mark for mark in marks if mark >= 50))
print(max(len(name) for name in ["Ayesha", "Bilal"]))
output
160
6

No brackets needed when the generator is the only argument, and nothing larger than one value is ever held in memory.

When not to use one

A comprehension is for building a collection. It is not a shorter way to write a loop.

names = ["Ayesha", "Bilal"]

# wrong: this builds a list of Nones and throws it away
[print(name) for name in names]
output
Ayesha
Bilal

That works, and it is the wrong tool: it constructs a two-item list of Nones purely as a side effect of printing. Use a plain for loop when you want to do something rather than make something.

The other stopping point is length. When a comprehension needs a filter, a ternary and a nested loop, it has stopped being one readable line:

grid = [[1, 2], [3, 4], [5, 6]]

hard = [v * 2 if v % 2 == 0 else v for row in grid for v in row if v != 3]
print(hard)
output
[1, 4, 8, 5, 12]

Correct, and nobody wants to be the person who has to change it. Two levels of logic is the point at which a loop with a name and a comment is the kinder choice.

What to remember

  • [expr for x in items] — transform every item.
  • [expr for x in items if test] — filter, then transform.
  • [a if test else b for x in items] — the ternary goes at the front.
  • {k: v for ...} and {expr for ...} — dicts and sets.
  • (expr for ...) — a generator, consumed once.

And when it stops fitting comfortably on one line, that is the language telling you to use a loop.

Next: writing your own functions.

Prefer learning with a teacher?

WasiLearn Academy runs small-group classes covering this material, online and in Karachi.

See the classes