Advanced Comprehensions

Nested loops, conditionals, dict and set comprehensions — plus when to stop and write a loop.

You have met the basic list comprehension. Here we push it: multiple loops, filtering, transforming into dicts and sets, and — importantly — knowing when a comprehension has become too clever.

Nested loops read top-to-bottom

Multiple for clauses run left to right, exactly like nested loops written out:

pairs = [(x, y) for x in range(2) for y in range(2)]
print(pairs)   # [(0, 0), (0, 1), (1, 0), (1, 1)]

# Filter with if; transform with a conditional expression
labels = ['even' if n % 2 == 0 else 'odd' for n in range(4)]
print(labels)  # ['even', 'odd', 'even', 'odd']

Dict and set comprehensions

Swap the brackets: {k: v for ...} builds a dict, {expr for ...} builds a set (deduplicating as it goes). Inverting a dictionary or indexing a list by a key becomes a one-liner.

Know when to stop

A comprehension should fit in your head at a glance. Two for clauses and one if is usually the ceiling. Beyond that — or when you need a try, or an intermediate variable used twice — a plain loop is more readable, and readability wins.

Example

Example · python
orders = [
    {'id': 1, 'user': 'ada',   'total': 120, 'paid': True},
    {'id': 2, 'user': 'grace', 'total': 80,  'paid': False},
    {'id': 3, 'user': 'ada',   'total': 200, 'paid': True},
    {'id': 4, 'user': 'alan',  'total': 50,  'paid': True},
]

# dict comprehension: id -> total, only for paid orders
paid_totals = {o['id']: o['total'] for o in orders if o['paid']}
print('paid totals :', paid_totals)

# set comprehension: distinct users who paid
paying_users = {o['user'] for o in orders if o['paid']}
print('paying users:', sorted(paying_users))

# nested comprehension building a grouped structure
users = sorted({o['user'] for o in orders})
by_user = {u: [o['id'] for o in orders if o['user'] == u] for u in users}
print('orders/user :', by_user)

# generator expression straight into a reducer — no intermediate list
revenue = sum(o['total'] for o in orders if o['paid'])
print('revenue     :', revenue)
print('any unpaid? :', any(not o['paid'] for o in orders))

# Output:
# paid totals : {1: 120, 3: 200, 4: 50}
# paying users: ['ada', 'alan']
# orders/user : {'ada': [1, 3], 'alan': [4], 'grace': [2]}
# revenue     : 370
# any unpaid? : True

When to use it

  • A matrix transposer uses a nested list comprehension to swap rows and columns in one expression.
  • An inverted index is built as a dict comprehension mapping each word to its list of document IDs.
  • A pipeline switches from a list comprehension to a generator expression when processing millions of records to save memory.

More examples

Nested list comprehension

Transposes a 3x3 matrix with a nested comprehension: the outer iterates columns, inner iterates rows.

Example · python
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(3)]
print(transposed)
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

Dict comprehension with condition

Builds a filtered dict of only in-stock items using a dict comprehension with an if clause.

Example · python
inventory = {'apple': 0, 'banana': 5, 'cherry': 0, 'date': 3}
in_stock = {k: v for k, v in inventory.items() if v > 0}
print(in_stock)
# {'banana': 5, 'date': 3}

Generator expression vs list comprehension

Compares memory usage: a list comprehension allocates all values, a generator expression is constant size.

Example · python
import sys

big = range(1_000_000)
list_comp = [x ** 2 for x in big]     # materialises all 1M items
gen_expr  = (x ** 2 for x in big)     # lazy, computes on demand

print(sys.getsizeof(list_comp))   # ~8 MB
print(sys.getsizeof(gen_expr))    # ~200 bytes

Discussion

  • Be the first to comment on this lesson.