|$ curl https://forge-ai.dev/api/markdown?path=docs/python/comprehensions
$cat docs/python-—-comprehensions-&-generators.md
updated Recently·18 min read·published

Python — Comprehensions & Generators

PythonIntermediate
Introduction

Comprehensions provide a concise, readable, and performant way to create sequences. Generators enable lazy evaluation for memory-efficient iteration over large datasets.

List Comprehensions
list_comprehensions.py
Python
1# Basic syntax: [expression for item in iterable]
2squares = [x ** 2 for x in range(10)]
3# → [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
4
5# Equivalent manual loop:
6squares = []
7for x in range(10):
8 squares.append(x ** 2)
9
10# With condition: [expr for item in iterable if condition]
11evens = [x for x in range(20) if x % 2 == 0]
12# → [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
13
14# Multiple for clauses (nested loops)
15pairs = [(x, y) for x in [1, 2] for y in [3, 4]]
16# → [(1, 3), (1, 4), (2, 3), (2, 4)]
17
18# Equivalent nested loop:
19pairs = []
20for x in [1, 2]:
21 for y in [3, 4]:
22 pairs.append((x, y))
23
24# if-else in expression (not filter — ternary)
25result = ["even" if x % 2 == 0 else "odd" for x in range(5)]
26# → ["even", "odd", "even", "odd", "even"]
27
28# Flatten a matrix
29matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
30flat = [num for row in matrix for num in row]
31# → [1, 2, 3, 4, 5, 6, 7, 8, 9]
32
33# String operations
34words = ["hello", "world", "python"]
35upper = [w.upper() for w in words]
36# → ["HELLO", "WORLD", "PYTHON"]
37
38# With function calls
39import os
40files = [f for f in os.listdir(".") if f.endswith(".py")]
41
42# Nested comprehensions (transpose matrix)
43matrix = [[1, 2, 3], [4, 5, 6]]
44transposed = [[row[i] for row in matrix] for i in range(3)]
45# → [[1, 4], [2, 5], [3, 6]]

info

List comprehensions are usually faster than manual loops because they avoid repeated .append() calls and use C-level iteration internally. But don't sacrifice readability — if a comprehension spans multiple lines, a loop is clearer.
Dict & Set Comprehensions
dict_set_comprehensions.py
Python
1# Dict comprehension: {key: value for item in iterable}
2squares = {x: x ** 2 for x in range(5)}
3# → {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
4
5# With condition
6users = {"alice": 30, "bob": 25, "charlie": 35}
7adults = {name: age for name, age in users.items() if age >= 30}
8# → {"alice": 30, "charlie": 35}
9
10# Transform keys/values
11swapped = {value: key for key, value in users.items()}
12# → {30: "alice", 25: "bob", 35: "charlie"}
13
14# From two lists (zip)
15names = ["alice", "bob", "charlie"]
16ages = [30, 25, 35]
17user_dict = {name: age for name, age in zip(names, ages)}
18
19# Set comprehension {expr for item in iterable}
20unique_lengths = {len(w) for w in ["hello", "world", "hi", "hey"]}
21# → {2, 3, 5} (duplicates removed)
22
23# Filter with set
24evens_set = {x for x in range(10) if x % 2 == 0}
25# → {0, 2, 4, 6, 8}
Generators

Generators produce values lazily — they compute each value on demand, making them memory-efficient for large or infinite sequences.

generators.py
Python
1# Generator expression: (expr for item in iterable)
2squares_gen = (x ** 2 for x in range(1_000_000))
3
4# Nothing computed yet — just an object
5print(squares_gen) # → <generator object <genexpr> at 0x...>
6
7# Get values one at a time
8print(next(squares_gen)) # → 0
9print(next(squares_gen)) # → 1
10print(next(squares_gen)) # → 4
11
12# Or consume all (defeats lazy purpose if large)
13squares_list = list(squares_gen) # won't include first 3
14
15# Generator function — using yield
16def fibonacci(n: int):
17 """Generate n Fibonacci numbers."""
18 a, b = 0, 1
19 for _ in range(n):
20 yield a
21 a, b = b, a + b
22
23for num in fibonacci(10):
24 print(num, end=" ") # → 0 1 1 2 3 5 8 13 21 34
25
26# Generator for an infinite sequence
27def count_from(start: int = 0):
28 while True:
29 yield start
30 start += 1
31
32# Take first 5 from infinite generator
33counter = count_from(10)
34for _ in range(5):
35 print(next(counter)) # → 10 11 12 13 14
36
37# Generator pipeline — powerful pattern
38def read_lines(filename: str):
39 """Lazily read lines from a file."""
40 with open(filename) as f:
41 yield from f # Python 3.3+: delegate to another generator
42
43def filter_comments(lines):
44 for line in lines:
45 stripped = line.strip()
46 if stripped and not stripped.startswith("#"):
47 yield stripped
48
49# Chain generators (no intermediate lists!)
50lines = read_lines("config.txt")
51config = filter_comments(lines)
52for entry in config:
53 process(entry)
54
55# yield from — delegate to sub-iterator
56def chain_generators(*gens):
57 for gen in gens:
58 yield from gen
59
60combined = chain_generators(
61 fibonacci(5),
62 fibonacci(5),
63)
64print(list(combined)) # → [0,1,1,2,3, 0,1,1,2,3]

best practice

Use generators when working with large datasets, file streams, or infinite sequences. They reduce memory from O(n) to O(1). A common pattern is generator pipelines — chain transformations without creating intermediate lists.
Performance Comparison
ApproachMemorySpeedReadabilityUse Case
Manual loopHigh (list)SlowestGood for complex logicComplex multi-step transforms
ComprehensionHigh (list)FastestExcellent for simple casesSimple filter + transform
Generator exprLow (lazy)FastGoodLarge or infinite sequences
map/filterLow (lazy)ComparablePoor (lambda syntax)Pre-existing callables
performance.py
Python
1# Benchmark: squares of 1M numbers
2import timeit
3
4def test_loop():
5 result = []
6 for i in range(1_000_000):
7 result.append(i ** 2)
8 return result
9
10def test_comprehension():
11 return [i ** 2 for i in range(1_000_000)]
12
13def test_generator():
14 return (i ** 2 for i in range(1_000_000)) # instant — lazy
15
16# Comprehension is ~15-30% faster than manual loop
17# Generator is instant (O(1) time, O(1) memory) but lazy
Advanced Generator Patterns
advanced_generators.py
Python
1# Generator.send() — two-way communication
2def echo():
3 """Echo whatever is sent to the generator."""
4 while True:
5 received = yield
6 print(f"echo: {received}")
7
8gen = echo()
9next(gen) # prime the generator
10gen.send("hello") # → echo: hello
11gen.send("world") # → echo: world
12gen.close() # stop the generator
13
14# Generator.throw() — inject exceptions
15def catch_errors():
16 try:
17 while True:
18 yield "ok"
19 except ValueError:
20 yield "caught value error"
21
22gen = catch_errors()
23print(next(gen)) # → ok
24print(gen.throw(ValueError)) # → caught value error
25
26# Coroutine pattern with yield from (Python 3.3+)
27# (asyncio largely replaced this pattern, but still useful)
28
29# itertools — essential generator tools
30import itertools
31
32# Chain multiple iterables
33list(itertools.chain([1, 2], [3, 4])) # → [1, 2, 3, 4]
34
35# Infinite counters
36for i in itertools.count(10, 2): # 10, 12, 14, ...
37 if i > 20: break
38
39# Cycle
40for item in itertools.cycle(["A", "B", "C"]): # A, B, C, A, B, C...
41 break # infinite, so break
42
43# Repeat
44list(itertools.repeat("x", 3)) # → ["x", "x", "x"]
45
46# Combinations and permutations
47list(itertools.permutations([1, 2, 3], 2))
48# → [(1,2), (1,3), (2,1), (2,3), (3,1), (3,2)]
49
50list(itertools.combinations([1, 2, 3], 2))
51# → [(1,2), (1,3), (2,3)]
52
53# Groupby — group sorted iterable
54data = [("a", 1), ("a", 2), ("b", 3)]
55for key, group in itertools.groupby(data, key=lambda x: x[0]):
56 print(key, list(group))
57
58# islice — slice an iterator
59list(itertools.islice(range(100), 10, 20, 2)) # → [10, 12, 14, 16, 18]
$Blueprint — Engineering Documentation·Section ID: PYTHON-COMP·Revision: 1.0