List and Dict Comprehensions in Python

Published Updated

A comprehension builds a new collection from an existing one in a single expression. It replaces the create-empty, loop, append pattern with one line that states the result rather than the steps.

squares = [n * n for n in range(6)]

print(squares)
# Program output:
# [0, 1, 4, 9, 16, 25]

The Syntax

The List Form

The list form is written in square brackets and has three parts, read left to right:

# [ expression   for item in iterable   if condition ]
#   what to keep   where it comes from   which ones survive

The expression is evaluated once for each item that passes any filters, and its result goes into the new list. The for clause names the loop variable and the iterable it walks. The if clause is optional and decides which items reach the expression at all.

names = ["  Ada ", "bo", " Cleo"]

cleaned = [name.strip().title() for name in names]

print(cleaned)
# Program output:
# ['Ada', 'Bo', 'Cleo']

The source can be any iterable, not only a list. Strings, ranges, files, and dictionary views all work, and the result is always a new list rather than a change to the source.

The Dict Form

The dict form swaps the square brackets for braces and the single expression for a key and value separated by a colon:

# { key_expression: value_expression   for item in iterable   if condition }
words = ["apple", "fig", "banana"]

lengths = {word: len(word) for word in words}

print(lengths)
# Program output:
# {'apple': 5, 'fig': 3, 'banana': 6}

Feeding it .items() is how you transform a dictionary you already have. Unpacking the pair into two names keeps the body readable:

prices = {"apple": 1.5, "fig": 3.0}

doubled = {name: price * 2 for name, price in prices.items()}
inverted = {price: name for name, price in prices.items()}

print(doubled)
print(inverted)
# Program output:
# {'apple': 3.0, 'fig': 6.0}
# {1.5: 'apple', 3.0: 'fig'}

A lossless inversion requires the values to be hashable and distinct. Repeated values still run, and they collapse, which is the collision pitfall below.

Filtering Items

An if after the for clause decides which items get through. Items that fail it never reach the expression and never appear in the result:

scores = [85, 42, 91, 67, 95]

passing = [score for score in scores if score >= 70]

print(passing)
# Program output:
# [85, 91, 95]

The same clause works in the dict form, and it can test either half of the pair:

scores = {"Ada": 91, "Bo": 67, "Cleo": 85}

top = {name: score for name, score in scores.items() if score >= 85}

print(top)
# Program output:
# {'Ada': 91, 'Cleo': 85}

Choosing a Value Conditionally

An if with an else is a different thing in a different place. It sits inside the expression, before the for clause, and it picks a value rather than filtering items:

scores = [85, 42, 91]

labels = ["pass" if score >= 70 else "fail" for score in scores]

print(labels)
# Program output:
# ['pass', 'fail', 'pass']

The position tells you which one you are reading. A bare if at the end drops items, and an if-else at the front keeps every item and changes what is stored. Both can appear in one comprehension, though that is usually the point to switch to a loop.

Nesting and Flattening

Two for clauses in one comprehension read in the same order you would write the nested loops. The outer loop comes first:

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

flat = [value for row in matrix for value in row]

print(flat)
# Program output:
# [1, 2, 3, 4, 5, 6]

Building a nested structure is the other direction, and there the inner comprehension sits inside the expression:

grid = [[row * 3 + col for col in range(3)] for row in range(2)]

print(grid)
# Program output:
# [[0, 1, 2], [3, 4, 5]]

Those two look similar and mean opposite things. Two for clauses at the same level flatten, while a comprehension inside the expression nests. Beyond two levels, a named helper function is easier to read than either.

When does a loop read better?

A comprehension states one transformation. As soon as the body needs more than that, a loop says the same thing more plainly.

  • Two collections at once. One comprehension produces one result, so splitting items into passes and failures needs either two comprehensions over the same data or one loop.
  • Branching logic. A chain of conditions choosing between several values fits an if-elif-else block and does not fit an expression.
  • Error handling. The syntax has no room for try, so anything that can raise needs a loop or a helper function.
  • Side effects. Printing, writing files, and calling an API are not what a comprehension is for, and using one that way builds a list of None nobody wants.
  • Length. Once the line wraps across several lines and needs its own indentation to be legible, the loop it replaced was shorter.

The working rule is that a reader should get the result from one pass over the line. If they have to trace it, write the loop.

Pitfalls and Debugging

A Comprehension Used for Its Side Effect

Writing [print(x) for x in items] prints, and it also builds and discards a list of None values. Nothing raises, so the cost is invisible until the iterable is large:

items = ["a", "b"]

wasted = [print(item) for item in items]
print(wasted)
# Program output:
# a
# b
# [None, None]

The rule is that a comprehension is for producing a value. When you are not keeping the result, write for item in items: instead.

The Filter and the Value Swapped

Putting a bare if where an if-else belongs is a syntax error, which is the friendly version of this mistake:

# SyntaxError: expected 'else' after 'if' expression
# labels = [ "pass" if score >= 70 for score in scores ]

The unfriendly version compiles. Writing the if-else at the end where a filter belongs produces a result the same length as the input when you expected a shorter one, and no error appears anywhere.

Keys That Collide

A dict comprehension whose key expression repeats keeps the last pair silently, because that is what assigning to an existing key does:

people = [("admin", "Ada"), ("user", "Bo"), ("admin", "Cleo")]

by_role = {role: name for role, name in people}

print(by_role)
# Program output:
# {'admin': 'Cleo', 'user': 'Bo'}

Ada is gone and nothing said so. When keys can repeat, the data wanted a grouping loop that appends to a list per key, not a comprehension that stores one value per key.

Frequently Asked Questions

Are comprehensions faster than loops?

Usually a little, because the list is built without a separate method call on each pass. The gap is small enough that it is never the reason to choose one, and a comprehension whose body calls an expensive function is dominated by that function either way.

Can a comprehension use try and except?

No. Comprehension syntax accepts only for clauses, if filters, and expressions, so error handling has to live in a plain loop or in a helper function the comprehension calls. Pushing it into a helper keeps the comprehension readable when only one step can fail.

Does the loop variable leak out of the comprehension?

Not in Python 3. Each comprehension runs in its own scope, so the loop name is gone afterwards and does not overwrite an existing variable of the same name. That was true of Python 2 list comprehensions, which is where the belief comes from.

Is there a set comprehension too?

Yes, written with braces and a single expression rather than a key and value pair, as in {word.lower() for word in words}. It de-duplicates as it builds, which makes it the shortest route from a messy iterable to a set of distinct values.

How do you write a comprehension over two lists?

Use zip when the lists are paired position by position, as in {name: score for name, score in zip(names, scores)}. Use two for clauses when you want every combination instead, which produces the product of the two lengths rather than the shorter one.

What is a generator expression?

The same syntax in round brackets, which produces values on demand instead of building the whole collection. Use it when the result is consumed once, such as inside sum or any, because it avoids holding every intermediate value in memory.

Sources

  1. [1]
  2. [2]