Generators in Python
Generators
Section titled “Generators”Introduction
Section titled “Introduction”A generator is a function that produces a sequence of values lazily using yield instead of return. Generators are memory-efficient because they produce values on demand.
Generator Lazy Evaluation
Section titled “Generator Lazy Evaluation”flowchart TB Start["def countdown(n):while n > 0:"] --> Yield["⏸️ yield nPause and return n"] Yield -->|"next() called"| Resume["▶️ Resume from yieldn -= 1continue loop"] Resume -->|"n > 0"| Yield Resume -->|"n == 0"| Stop["⛔ StopIterationGenerator exhausted"]
subgraph Memory["Memory Efficient" Mem["🧠 Only stores current statenot the entire sequence!List: O(n) memoryGenerator: O(1) memory"] end
style Start fill:#7c3aed,color:#fff style Yield fill:#f59e0b,color:#000 style Resume fill:#4f46e5,color:#fff style Stop fill:#dc2626,color:#fff style Mem fill:#059669,color:#fff style Memory fill:#1e1b4b,color:#e0e7ffGenerator Functions
Section titled “Generator Functions”def countdown(n): while n > 0: yield n n -= 1
for num in countdown(5): print(num) # 5, 4, 3, 2, 1
# Generator objectsgen = countdown(3)print(next(gen)) # 3print(next(gen)) # 2print(next(gen)) # 1# print(next(gen)) # StopIterationGenerator Expressions
Section titled “Generator Expressions”# List comprehension (eager — creates full list)squares_list = [x ** 2 for x in range(10)]
# Generator expression (lazy — produces on demand)squares_gen = (x ** 2 for x in range(10))
print(squares_gen) # <generator object <genexpr> at 0x...>print(next(squares_gen)) # 0print(next(squares_gen)) # 1print(list(squares_gen)) # [4, 9, 16, 25, 36, 49, 64, 81]
# Memory comparisonimport syslist_comp = [x for x in range(1000000)]gen_expr = (x for x in range(1000000))print(sys.getsizeof(list_comp)) # ~8 MBprint(sys.getsizeof(gen_expr)) # ~200 bytes!yield from
Section titled “yield from”def chain(*iterables): for iterable in iterables: yield from iterable # Delegate to another iterable
result = list(chain([1, 2, 3], "abc", range(4, 7)))print(result) # [1, 2, 3, 'a', 'b', 'c', 4, 5, 6]
# Without yield fromdef chain_manual(*iterables): for iterable in iterables: for item in iterable: yield itemGenerator Pipelines
Section titled “Generator Pipelines”def read_lines(filename): with open(filename) as f: for line in f: yield line.strip()
def filter_lines(lines, pattern): for line in lines: if pattern in line: yield line
def transform_lines(lines): for line in lines: yield line.upper()
# Pipelinelines = read_lines("data.txt")filtered = filter_lines(lines, "ERROR")transformed = transform_lines(filtered)
for line in transformed: print(line)
# All processing is lazy — one line at a time!Infinite Generators
Section titled “Infinite Generators”def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b
# Take what you needfrom itertools import isliceprint(list(islice(fibonacci(), 10)))# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]send() and Generator Coroutines
Section titled “send() and Generator Coroutines”def accumulator(): total = 0 while True: value = yield total # Receive value via send() total += value
acc = accumulator()next(acc) # Prime the generatorprint(acc.send(10)) # 10print(acc.send(20)) # 30print(acc.send(30)) # 60Best Practices
Section titled “Best Practices”- Use generators for large/infinite sequences — save memory
- Use generator expressions for simple lazy iterations
- Use
yield fromto delegate to sub-generators - Build generator pipelines for data processing chains
- Know when to use list vs generator — generators are single-use!
Practice Exercises
Section titled “Practice Exercises”Exercise 1: Write a generator that yields all prime numbers indefinitely.
Exercise 2: Create a log file parser pipeline: read lines → filter ERROR → extract timestamps.
Exercise 3: Implement a generator that reads a large CSV file in chunks and yields batches of rows.