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
Jr Codex Python Notes
Level: Intermediate Prerequisites: Chapter 4 Time to complete: ~25 minutes
Table of Contents
- Why Comprehensions?
- List Comprehensions
- Adding Conditions
- Dictionary Comprehensions
- Set Comprehensions
- Generator Expressions
- Nested Comprehensions
- When NOT to Use a Comprehension
- 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| Pattern | Purpose |
|---|---|
[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 functionGenerator 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
- What's the difference between
[n**2 for n in range(5)]and(n**2 for n in range(5))? - Where does the filtering
ifgo in a comprehension, versus the value-choosing ternaryif/else? - 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