|$ 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
◆Python◆Intermediate
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] |
| 2 | squares = [x ** 2 for x in range(10)] |
| 3 | # → [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] |
| 4 | |
| 5 | # Equivalent manual loop: |
| 6 | squares = [] |
| 7 | for x in range(10): |
| 8 | squares.append(x ** 2) |
| 9 | |
| 10 | # With condition: [expr for item in iterable if condition] |
| 11 | evens = [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) |
| 15 | pairs = [(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: |
| 19 | pairs = [] |
| 20 | for x in [1, 2]: |
| 21 | for y in [3, 4]: |
| 22 | pairs.append((x, y)) |
| 23 | |
| 24 | # if-else in expression (not filter — ternary) |
| 25 | result = ["even" if x % 2 == 0 else "odd" for x in range(5)] |
| 26 | # → ["even", "odd", "even", "odd", "even"] |
| 27 | |
| 28 | # Flatten a matrix |
| 29 | matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] |
| 30 | flat = [num for row in matrix for num in row] |
| 31 | # → [1, 2, 3, 4, 5, 6, 7, 8, 9] |
| 32 | |
| 33 | # String operations |
| 34 | words = ["hello", "world", "python"] |
| 35 | upper = [w.upper() for w in words] |
| 36 | # → ["HELLO", "WORLD", "PYTHON"] |
| 37 | |
| 38 | # With function calls |
| 39 | import os |
| 40 | files = [f for f in os.listdir(".") if f.endswith(".py")] |
| 41 | |
| 42 | # Nested comprehensions (transpose matrix) |
| 43 | matrix = [[1, 2, 3], [4, 5, 6]] |
| 44 | transposed = [[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} |
| 2 | squares = {x: x ** 2 for x in range(5)} |
| 3 | # → {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} |
| 4 | |
| 5 | # With condition |
| 6 | users = {"alice": 30, "bob": 25, "charlie": 35} |
| 7 | adults = {name: age for name, age in users.items() if age >= 30} |
| 8 | # → {"alice": 30, "charlie": 35} |
| 9 | |
| 10 | # Transform keys/values |
| 11 | swapped = {value: key for key, value in users.items()} |
| 12 | # → {30: "alice", 25: "bob", 35: "charlie"} |
| 13 | |
| 14 | # From two lists (zip) |
| 15 | names = ["alice", "bob", "charlie"] |
| 16 | ages = [30, 25, 35] |
| 17 | user_dict = {name: age for name, age in zip(names, ages)} |
| 18 | |
| 19 | # Set comprehension {expr for item in iterable} |
| 20 | unique_lengths = {len(w) for w in ["hello", "world", "hi", "hey"]} |
| 21 | # → {2, 3, 5} (duplicates removed) |
| 22 | |
| 23 | # Filter with set |
| 24 | evens_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) |
| 2 | squares_gen = (x ** 2 for x in range(1_000_000)) |
| 3 | |
| 4 | # Nothing computed yet — just an object |
| 5 | print(squares_gen) # → <generator object <genexpr> at 0x...> |
| 6 | |
| 7 | # Get values one at a time |
| 8 | print(next(squares_gen)) # → 0 |
| 9 | print(next(squares_gen)) # → 1 |
| 10 | print(next(squares_gen)) # → 4 |
| 11 | |
| 12 | # Or consume all (defeats lazy purpose if large) |
| 13 | squares_list = list(squares_gen) # won't include first 3 |
| 14 | |
| 15 | # Generator function — using yield |
| 16 | def 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 | |
| 23 | for 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 |
| 27 | def count_from(start: int = 0): |
| 28 | while True: |
| 29 | yield start |
| 30 | start += 1 |
| 31 | |
| 32 | # Take first 5 from infinite generator |
| 33 | counter = count_from(10) |
| 34 | for _ in range(5): |
| 35 | print(next(counter)) # → 10 11 12 13 14 |
| 36 | |
| 37 | # Generator pipeline — powerful pattern |
| 38 | def 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 | |
| 43 | def 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!) |
| 50 | lines = read_lines("config.txt") |
| 51 | config = filter_comments(lines) |
| 52 | for entry in config: |
| 53 | process(entry) |
| 54 | |
| 55 | # yield from — delegate to sub-iterator |
| 56 | def chain_generators(*gens): |
| 57 | for gen in gens: |
| 58 | yield from gen |
| 59 | |
| 60 | combined = chain_generators( |
| 61 | fibonacci(5), |
| 62 | fibonacci(5), |
| 63 | ) |
| 64 | print(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
| Approach | Memory | Speed | Readability | Use Case |
|---|---|---|---|---|
| Manual loop | High (list) | Slowest | Good for complex logic | Complex multi-step transforms |
| Comprehension | High (list) | Fastest | Excellent for simple cases | Simple filter + transform |
| Generator expr | Low (lazy) | Fast | Good | Large or infinite sequences |
| map/filter | Low (lazy) | Comparable | Poor (lambda syntax) | Pre-existing callables |
performance.py
Python
| 1 | # Benchmark: squares of 1M numbers |
| 2 | import timeit |
| 3 | |
| 4 | def test_loop(): |
| 5 | result = [] |
| 6 | for i in range(1_000_000): |
| 7 | result.append(i ** 2) |
| 8 | return result |
| 9 | |
| 10 | def test_comprehension(): |
| 11 | return [i ** 2 for i in range(1_000_000)] |
| 12 | |
| 13 | def 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 |
| 2 | def echo(): |
| 3 | """Echo whatever is sent to the generator.""" |
| 4 | while True: |
| 5 | received = yield |
| 6 | print(f"echo: {received}") |
| 7 | |
| 8 | gen = echo() |
| 9 | next(gen) # prime the generator |
| 10 | gen.send("hello") # → echo: hello |
| 11 | gen.send("world") # → echo: world |
| 12 | gen.close() # stop the generator |
| 13 | |
| 14 | # Generator.throw() — inject exceptions |
| 15 | def catch_errors(): |
| 16 | try: |
| 17 | while True: |
| 18 | yield "ok" |
| 19 | except ValueError: |
| 20 | yield "caught value error" |
| 21 | |
| 22 | gen = catch_errors() |
| 23 | print(next(gen)) # → ok |
| 24 | print(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 |
| 30 | import itertools |
| 31 | |
| 32 | # Chain multiple iterables |
| 33 | list(itertools.chain([1, 2], [3, 4])) # → [1, 2, 3, 4] |
| 34 | |
| 35 | # Infinite counters |
| 36 | for i in itertools.count(10, 2): # 10, 12, 14, ... |
| 37 | if i > 20: break |
| 38 | |
| 39 | # Cycle |
| 40 | for item in itertools.cycle(["A", "B", "C"]): # A, B, C, A, B, C... |
| 41 | break # infinite, so break |
| 42 | |
| 43 | # Repeat |
| 44 | list(itertools.repeat("x", 3)) # → ["x", "x", "x"] |
| 45 | |
| 46 | # Combinations and permutations |
| 47 | list(itertools.permutations([1, 2, 3], 2)) |
| 48 | # → [(1,2), (1,3), (2,1), (2,3), (3,1), (3,2)] |
| 49 | |
| 50 | list(itertools.combinations([1, 2, 3], 2)) |
| 51 | # → [(1,2), (1,3), (2,3)] |
| 52 | |
| 53 | # Groupby — group sorted iterable |
| 54 | data = [("a", 1), ("a", 2), ("b", 3)] |
| 55 | for key, group in itertools.groupby(data, key=lambda x: x[0]): |
| 56 | print(key, list(group)) |
| 57 | |
| 58 | # islice — slice an iterator |
| 59 | list(itertools.islice(range(100), 10, 20, 2)) # → [10, 12, 14, 16, 18] |
$Blueprint — Engineering Documentation·Section ID: PYTHON-COMP·Revision: 1.0