Comprehensions & Generators

Python Intermediate — Comprehensions & Generators
Python Intermediate
Course 2 · Chapter 5 · Comprehensions & Generators

⚙️ Comprehensions & Generators

Course 1 gave you a first look at list comprehensions (Chapter 6) and dict comprehensions (Chapter 7). This chapter goes deeper: set comprehensions, nested comprehensions, and — the real payoff — generator expressions and generator functions with yield, which produce values lazily instead of building an entire collection in memory up front.

🎯 Set Comprehensions

The same {expr for item in iterable} shape works for sets too, just with automatic deduplication:

words = ["apple", "banana", "apple", "cherry"] lengths = {len(word) for word in words} print(lengths) # {5, 6} — duplicate 5s from "apple" and "apple" collapse to one

🪆 Nested Comprehensions

matrix = [[1, 2, 3], [4, 5, 6]] # flatten a list of lists into one list: flat = [n for row in matrix for n in row] print(flat) # [1, 2, 3, 4, 5, 6]

Reading order matters: the for clauses read left to right exactly like nested for loops would — for row in matrix is the outer loop, for n in row is the inner one. Written as loops, this would be:

flat = [] for row in matrix: for n in row: flat.append(n)

🌊 Generator Expressions

A generator expression looks almost identical to a list comprehension, but with parentheses instead of square brackets — and it computes values one at a time, on demand, rather than building the whole list up front:

squares_list = [n ** 2 for n in range(1000000)] # builds all 1,000,000 values NOW, in memory squares_gen = (n ** 2 for n in range(1000000)) # computes each value only when asked print(type(squares_gen)) # <class 'generator'> print(next(squares_gen)) # 0 — the first value, computed just now print(next(squares_gen)) # 1 — the second value

For a million items, the list comprehension allocates memory for all million numbers immediately; the generator expression holds almost no memory at all — just enough to know where it left off, computing the next value only when next() (or a for loop) asks for it.

⏸️ Generator Functions and yield

def count_up_to(n): i = 1 while i <= n: yield i # pauses here, hands back i, remembers where it was i += 1 for num in count_up_to(5): print(num) # 1 2 3 4 5

Any function containing yield becomes a generator function — calling it doesn't run the body immediately, it returns a generator object. Each call to next() (which a for loop does automatically) resumes the function right where it last paused, runs until the next yield, and hands back that value — the function's local state (like i here) is preserved between calls, something a regular function can't do at all.

⚠ A Generator Is Exhausted After One Full Pass
gen = count_up_to(3) print(list(gen)) # [1, 2, 3] print(list(gen)) # [] — empty! the generator is already exhausted

Unlike a list, a generator can only be iterated once — once every value has been produced, it's done, and iterating it again yields nothing. If you need to loop over the same values twice, either use a list/list comprehension instead, or call the generator function again to get a fresh generator object.

Lazy Iteration: Go vs Kotlin vs Python

LanguageApproach
GoNo generator syntax — lazy, on-demand value production is typically built manually with goroutines and channels, a heavier-weight tool for the same underlying idea.
Kotlinsequence { yield(value) } — genuinely close to Python's yield, producing a lazy Sequence the same way Python's generator functions produce a lazy generator.
Pythonyield inside any function, or a generator expression (x for x in ...).

Kotlin's sequence { yield(...) } is one of the closer direct parallels to a Python feature covered so far in this course — the underlying "pause and resume, producing values lazily" mechanism is essentially the same idea in different syntax.

Set comprehension

{expr for item in iterable} — same shape as list/dict comprehensions.

Nested comprehension

Multiple for clauses read left to right, like nested loops.

Generator expression

(expr for item in iterable) — lazy, memory-efficient, one value at a time.

yield

Pauses a function, hands back a value, and resumes exactly there on the next call.

💻 Coding Challenges

Challenge 1: Unique Word Lengths

Given sentence = "the quick brown fox jumps over the lazy dog", use a set comprehension to find the set of unique word lengths appearing in the sentence.

Goal: Practice a set comprehension combined with .split() from Course 1.

→ Solution

Challenge 2: Flatten and Filter

Given matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], use a single nested comprehension (with an if condition) to produce a flat list of only the even numbers: [2, 4, 6, 8].

Goal: Practice combining nested comprehensions with a filtering condition in one expression.

→ Solution

Challenge 3: A Fibonacci Generator

Write a generator function fibonacci(n) that yields the first n numbers of the Fibonacci sequence (0, 1, 1, 2, 3, 5, ...), one at a time. Use a for loop to print all values from fibonacci(8).

Goal: Practice writing a generator function with yield that maintains state across calls.

→ Solution

🎯 What's Next

Next chapter: Decorators — function decorators, @property, and functools (wraps, lru_cache).