That one-line Python syntax that looks backwards until you read it in the right order. Here is the plain-English translation.
List comprehensions are the syntax that makes new Python developers feel they are reading a foreign language: square brackets, a for loop squeezed onto one line, sometimes an if at the end. The good news is that a comprehension is just a normal loop written more compactly. Once you learn to read it in the right order, it stops looking backwards.
By the end you will be able to:
- Turn any simple
forloop that builds a list into a comprehension, and back again. - Read a comprehension out loud in the right order.
- Filter with
if, and know howif / elsediffers. - Decide when a plain loop is the better choice.
You need to know what a Python list and a for loop are. Everything below can be tried in a Python shell or a file.
Start with the regular loop
Say you have a list of numbers and want a new list with the square of each. Written the long way, you make an empty list, loop over the numbers, and add each answer to it:
numbers = [1, 2, 3, 4, 5, 6]
squares = []
for n in numbers:
squares.append(n * n)
print(squares) # [1, 4, 9, 16, 25, 36]Notice the pattern: (1) start with an empty list, (2) loop over something, (3) put something into the list each time. That three-part pattern is exactly what a list comprehension squeezes into one line.
The same thing as a list comprehension
Here is the exact same job as a comprehension:
squares = [n * n for n in numbers]
print(squares) # [1, 4, 9, 16, 25, 36]How to read it
The trick is that you do not read it left to right. Start with the loop, then the expression, then the brackets:
Said in plain English: “For each n in numbers, work out n times n, and put them all in a list.” The general shape is:
[ what_to_keep for item in something ]Adding a condition
You can also filter while you build by adding an if at the end. Only the items where the condition is true are used. First the loop version, then the short one:
evens = []
for n in numbers:
if n % 2 == 0:
evens.append(n)evens = [n for n in numbers if n % 2 == 0]
print(evens) # [2, 4, 6]The reading order is the same: loop through numbers, keep only the ones where the condition is true, and collect them. (n % 2 is the remainder after dividing by 2, so it is 0 for even numbers.)
Change and filter together
You can do both at once. The if decides which items get in, and the expression at the front decides what they look like:
even_squares = [n * n for n in numbers if n % 2 == 0]
print(even_squares) # [4, 16, 36]if versus if / else
This is the number one thing that confuses people. There are two different places an if can go, and they do different jobs:
| Where the if goes | What it does | Example |
|---|---|---|
| At the end, after the loop | Filters: skips items, so the result can be shorter | [n for n in numbers if n % 2 == 0] |
At the front, with an else | Chooses a value for every item, so the length stays the same | ["even" if n % 2 == 0 else "odd" for n in numbers] |
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
# ['odd', 'even', 'odd', 'even', 'odd', 'even']More things you can do
The item does not have to be a number. Comprehensions are very handy for cleaning up text:
words = [" Apple ", "fig", " Kiwi"]
clean = [w.strip().lower() for w in words]
# ['apple', 'fig', 'kiwi']You can use two for parts to flatten a list of lists. Read it like two nested loops, outer loop first:
grid = [[1, 2], [3, 4], [5]]
flat = [n for row in grid for n in row]
# [1, 2, 3, 4, 5]The same idea works with other brackets. Curly braces make a dictionary or a set, and round brackets (inside a function call) make a generator that produces values one at a time without building the whole list in memory:
lengths = {w: len(w) for w in ["apple", "fig", "kiwi"]} # dict
# {'apple': 5, 'fig': 3, 'kiwi': 4}
remainders = {n % 3 for n in numbers} # set
# {0, 1, 2}
total = sum(n * n for n in numbers) # generator
# 91When to use one, and when not to
A comprehension is great when the logic fits comfortably on one line: a simple change, a simple filter, or both. It is not a rule you must follow everywhere. If it makes the code harder to read, use a loop.
# This is too much for one line:
result = [transform(x) for x in items if x.ok and x.size > 3 and not x.hidden]
# Clearer as a loop:
result = []
for x in items:
if not x.ok:
continue
if x.size <= 3 or x.hidden:
continue
result.append(transform(x))Common mistakes
| What happens | Why | Fix |
|---|---|---|
SyntaxError on [n for n in numbers else 0] | else cannot go at the end | Move it to the front: [n if n > 0 else 0 for n in numbers] |
| The result is shorter than you expected | The if at the end filters items out | Use a if condition else b at the front to keep every item |
You get None values in the list | You used a function that returns nothing, such as print, as the expression | Put the value you want in the list at the front |
| The loop order in a nested one is wrong | Two for parts read like nested loops, outer first | Write the loops out in full, then copy them across in the same order |
| Nobody can read it | Too much on one line | Switch to a loop with a clear name for each step |
Try it yourself
1. From [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], build a list of the even numbers doubled.
Show answer
[n * 2 for n in range(1, 11) if n % 2 == 0]
# [4, 8, 12, 16, 20]2. From ["apple", "fig", "banana", "kiwi"], keep the words longer than 3 letters.
Show answer
words = ["apple", "fig", "banana", "kiwi"]
[w for w in words if len(w) > 3]
# ['apple', 'banana', 'kiwi']3. Rewrite this loop as a comprehension: result = [], then for w in words: result.append(len(w)).
Show answer
result = [len(w) for w in words]