Python

Functions And Functional Basics

Comprehensions

A comprehension builds a new collection (list, dict, or set) from an existing iterable, in a single, readable expression — replacing a common 3-4 line loop patt

JrCodex·7 min read

Jr Codex Python Notes

Level: Intermediate Prerequisites: Chapter 4 Time to complete: ~25 minutes


Table of Contents

  1. Why Comprehensions?
  2. List Comprehensions
  3. Adding Conditions
  4. Dictionary Comprehensions
  5. Set Comprehensions
  6. Generator Expressions
  7. Nested Comprehensions
  8. When NOT to Use a Comprehension
  9. Summary & Next Steps

1. Why Comprehensions?

A comprehension builds a new collection (list, dict, or set) from an existing iterable, in a single, readable expression — replacing a common 3-4 line loop pattern.

# Traditional loop
squares = []
for n in range(1, 6):
    squares.append(n ** 2)
 
# List comprehension — same result, one line
squares = [n ** 2 for n in range(1, 6)]
print(squares)     # [1, 4, 9, 16, 25]
Comprehension Anatomy
─────────────────────────────────────────
  [ expression   for item in iterable ]
    ↑                ↑         ↑
  what to build   loop var   source data
─────────────────────────────────────────

2. List Comprehensions

numbers = [1, 2, 3, 4, 5]
 
doubled = [n * 2 for n in numbers]
print(doubled)          # [2, 4, 6, 8, 10]
 
words = ["hello", "world", "python"]
upper_words = [w.upper() for w in words]
print(upper_words)       # ['HELLO', 'WORLD', 'PYTHON']
 
lengths = [len(w) for w in words]
print(lengths)             # [5, 5, 6]

3. Adding Conditions

Filtering with if (at the end)

numbers = range(1, 11)
 
evens = [n for n in numbers if n % 2 == 0]
print(evens)         # [2, 4, 6, 8, 10]

Conditional expression (ternary, at the start)

Note the different position — filtering if goes at the end; the value-choosing ternary goes right after the expression:

numbers = range(1, 11)
 
labels = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(labels)     # ['odd', 'even', 'odd', 'even', ...]

Combining both

numbers = range(1, 21)
 
result = [n for n in numbers if n % 3 == 0 if n % 5 != 0]   # multiple filters
print(result)      # [3, 6, 9, 12, 18]  — divisible by 3, but not by 5
PatternPurpose
[expr for x in iterable]Transform every item
[expr for x in iterable if cond]Filter, then transform
[a if cond else b for x in iterable]Transform to one of two values based on a condition

4. Dictionary Comprehensions

Builds a dict using {key_expr: value_expr for ...} — briefly previewed in Module 1, Chapter 8:

names = ["Alice", "Bob", "Charlie"]
 
name_lengths = {name: len(name) for name in names}
print(name_lengths)      # {'Alice': 5, 'Bob': 3, 'Charlie': 7}
 
prices = {"apple": 1.0, "banana": 0.5, "cherry": 3.0}
discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(discounted)          # {'apple': 0.9, 'banana': 0.45, 'cherry': 2.7}
 
# Filtering a dict
expensive = {item: price for item, price in prices.items() if price > 1.0}
print(expensive)             # {'cherry': 3.0}
 
# Swapping keys and values
swapped = {v: k for k, v in name_lengths.items()}
print(swapped)                # {5: 'Alice', 3: 'Bob', 7: 'Charlie'}

5. Set Comprehensions

Same idea, but builds a set — automatically deduplicating, just like the set() from Module 1, Chapter 9:

words = ["apple", "banana", "apple", "cherry", "banana"]
 
unique_lengths = {len(w) for w in words}
print(unique_lengths)        # {5, 6} — only unique lengths, order not guaranteed
 
first_letters = {w[0] for w in words}
print(first_letters)           # {'a', 'b', 'c'}

6. Generator Expressions

Same syntax as a list comprehension, but with () instead of [] — and it produces items lazily, one at a time, instead of building the whole list in memory up front.

squares_list = [n ** 2 for n in range(1_000_000)]     # builds ALL 1M items in memory now
squares_gen  = (n ** 2 for n in range(1_000_000))       # builds items one at a time, on demand
 
print(type(squares_gen))     # <class 'generator'>
 
# Consume it with a loop or next()
gen = (n ** 2 for n in range(5))
print(next(gen))      # 0
print(next(gen))       # 1
for value in gen:        # continues from where next() left off: 4, 9, 16
    print(value)

When to use a generator expression: when you're processing a large (or infinite) sequence and only need to iterate once — e.g., summing values without ever holding the whole list in memory:

total = sum(n ** 2 for n in range(1_000_000))    # note: no extra parentheses needed
                                                    # when it's the sole argument to a function

Generator expressions are a lightweight preview of full generators (yield), covered in depth in Module 5.


7. Nested Comprehensions

Comprehensions can nest — but readability degrades fast, so use this sparingly.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
 
# Flatten a matrix into a single list
flat = [num for row in matrix for num in row]
print(flat)      # [1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# Transpose a matrix (rows become columns)
transposed = [[row[i] for row in matrix] for i in range(3)]
print(transposed)   # [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Reading a nested comprehension:
─────────────────────────────────────────
  [num for row in matrix for num in row]
        ↑         ↑ outer loop (runs first)
        └──────────── ↑ inner loop (runs for each row)
  Equivalent to:
    for row in matrix:
        for num in row:
            result.append(num)
─────────────────────────────────────────

8. When NOT to Use a Comprehension

Comprehensions are for building a new collection from a transform/filter. If the loop body has side effects, multiple statements, or complex branching, a regular for loop is clearer.

# Bad — comprehension used purely for side effects (discards the result!)
[print(n) for n in range(5)]     # works, but creates a throwaway list of None values
 
# Better — plain loop, no wasted list
for n in range(5):
    print(n)
 
# Bad — too much logic crammed into one line
result = [complex_transform(x) if validate(x) else fallback(x) for x in data if pre_check(x)]
 
# Better — a named function, called from a simple comprehension or loop
def process(x):
    if not pre_check(x):
        return None
    return complex_transform(x) if validate(x) else fallback(x)
 
result = [process(x) for x in data]

9. Summary & Next Steps

Key Takeaways

  • Comprehensions replace a common append-in-a-loop pattern with a single, declarative expression: [expr for x in iterable if cond].
  • List, dict ({k: v for ...}), and set ({expr for ...}) comprehensions all follow the same pattern, just with different brackets.
  • Generator expressions ((expr for ...)) produce values lazily — use them for large sequences you only need to iterate once.
  • Don't force a comprehension when the logic has side effects or gets too complex — a plain loop (or a helper function) is more readable.

Concept Check

  1. What's the difference between [n**2 for n in range(5)] and (n**2 for n in range(5))?
  2. Where does the filtering if go in a comprehension, versus the value-choosing ternary if/else?
  3. Why is [print(n) for n in range(5)] considered bad style even though it "works"?

Next Chapter

Chapter 6: Lambda Functions & map/filter/reduce


Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index