Generators
Generators are a simple and powerful tool for creating iterators in Python. They allow you to write functions that can pause and resume execution, making them perfect for creating sequences that are computed lazily (on-demand).
What are Generators?
A generator is a special type of iterator that is created using a function with yield statements instead of return. Generators are memory-efficient because they produce values one at a time rather than storing all values in memory.
Generator Functions
Generator functions are defined like regular functions but use yield instead of return. When called, they return a generator object (an iterator).
Basic Generator Function
def count_up(limit): current = 0 while current < limit: yield current current += 1
# Usagefor num in count_up(5): print(num) # 0, 1, 2, 3, 4
# Or manuallygen = count_up(3)print(next(gen)) # 0print(next(gen)) # 1print(next(gen)) # 2How yield Works
When Python encounters a yield statement:
- It returns the value to the caller
- It pauses the function’s execution
- It saves the function’s state (local variables, execution position)
- When
next()is called again, execution resumes from where it left off
def simple_generator(): print("Starting generator") yield 1 print("After first yield") yield 2 print("After second yield") yield 3 print("Generator finished")
gen = simple_generator()print("Created generator")
print(next(gen)) # Starting generator, then 1print(next(gen)) # After first yield, then 2print(next(gen)) # After second yield, then 3# print(next(gen)) # Generator finished, then StopIterationGenerator vs Regular Function
Regular Function
def squares_list(n): result = [] for i in range(n): result.append(i ** 2) return result # Returns all values at once
# All values stored in memorysquares = squares_list(1000000) # Uses significant memoryGenerator Function
def squares_generator(n): for i in range(n): yield i ** 2 # Yields one value at a time
# Values generated on-demandsquares = squares_generator(1000000) # Uses minimal memoryfor square in squares: if square > 100: break # Only computes what's neededCommon Generator Patterns
Fibonacci Generator
def fibonacci(limit=None): prev, curr = 0, 1 count = 0
while limit is None or count < limit: yield prev prev, curr = curr, prev + curr count += 1
# Generate first 10 Fibonacci numbersfor num in fibonacci(10): print(num) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34Infinite Generator
def natural_numbers(): num = 1 while True: yield num num += 1
# Generate natural numbersgen = natural_numbers()for i in range(5): print(next(gen)) # 1, 2, 3, 4, 5Generator with Multiple Yields
def alternating(): yield "even" yield "odd" yield "even" yield "odd"
for value in alternating(): print(value) # even, odd, even, oddGenerator with Conditions
def even_numbers(start, end): for num in range(start, end): if num % 2 == 0: yield num
for num in even_numbers(1, 10): print(num) # 2, 4, 6, 8Generator Expressions
Generator expressions are a concise way to create generators, similar to list comprehensions but with parentheses instead of square brackets.
Syntax
# List comprehension (creates a list)squares_list = [x ** 2 for x in range(5)]print(squares_list) # [0, 1, 4, 9, 16]
# Generator expression (creates a generator)squares_gen = (x ** 2 for x in range(5))print(squares_gen) # <generator object <genexpr> at 0x...>print(list(squares_gen)) # [0, 1, 4, 9, 16]Generator Expression Examples
# Simple generator expressionnumbers = (x for x in range(10))print(list(numbers)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# With conditionevens = (x for x in range(10) if x % 2 == 0)print(list(evens)) # [0, 2, 4, 6, 8]
# With transformationsquares = (x ** 2 for x in range(5))print(list(squares)) # [0, 1, 4, 9, 16]
# Nestedpairs = ((x, y) for x in range(3) for y in range(3))print(list(pairs)) # [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]Generator Methods
Generators have several useful methods:
send()
Sends a value into the generator and resumes execution:
def number_generator(): value = yield while True: value = yield value * 2
gen = number_generator()next(gen) # Start the generator
print(gen.send(5)) # 10print(gen.send(10)) # 20print(gen.send(3)) # 6throw()
Raises an exception inside the generator:
def simple_gen(): try: yield 1 yield 2 except ValueError: yield "Error caught"
gen = simple_gen()print(next(gen)) # 1print(gen.throw(ValueError)) # Error caughtclose()
Closes the generator:
def count(): try: num = 0 while True: yield num num += 1 except GeneratorExit: print("Generator closed")
gen = count()print(next(gen)) # 0print(next(gen)) # 1gen.close() # Generator closedGenerator Pipelines
Generators can be chained together to create powerful data processing pipelines:
def numbers(): for i in range(10): yield i
def square(nums): for num in nums: yield num ** 2
def even_only(nums): for num in nums: if num % 2 == 0: yield num
# Chain generatorspipeline = even_only(square(numbers()))print(list(pipeline)) # [0, 4, 16, 36, 64]Generator Advantages
- Memory Efficient: Only produces values when needed
- Lazy Evaluation: Computes values on-demand
- Clean Code: More readable than custom iterator classes
- Infinite Sequences: Can represent infinite sequences
- State Preservation: Automatically preserves function state
Memory Comparison
import sys
# List comprehension - stores all valueslist_comp = [x ** 2 for x in range(1000000)]print(sys.getsizeof(list_comp)) # Large size (millions of bytes)
# Generator expression - doesn't store valuesgen_expr = (x ** 2 for x in range(1000000))print(sys.getsizeof(gen_expr)) # Small size (few hundred bytes)Generator vs Iterator Class
Using Iterator Class
class CountDown: def __init__(self, start): self.current = start
def __iter__(self): return self
def __next__(self): if self.current <= 0: raise StopIteration self.current -= 1 return self.current + 1
for num in CountDown(5): print(num) # 5, 4, 3, 2, 1Using Generator Function
def count_down(start): current = start while current > 0: yield current current -= 1
for num in count_down(5): print(num) # 5, 4, 3, 2, 1The generator version is much simpler and more Pythonic!
Practical Examples
Reading Large Files
def read_large_file(filename): with open(filename, 'r') as file: for line in file: yield line.strip()
# Process file line by line without loading entire filefor line in read_large_file('large_file.txt'): process(line) # Process one line at a timeGenerating Combinations
def combinations(items, r): if r == 0: yield () else: for i in range(len(items)): for combo in combinations(items[i+1:], r-1): yield (items[i],) + combo
items = ['a', 'b', 'c', 'd']for combo in combinations(items, 2): print(combo) # ('a', 'b'), ('a', 'c'), ('a', 'd'), ('b', 'c'), ('b', 'd'), ('c', 'd')Sliding Window
def sliding_window(sequence, window_size): for i in range(len(sequence) - window_size + 1): yield sequence[i:i + window_size]
text = "Python"for window in sliding_window(text, 3): print(window) # Pyt, yth, tho, honDon’t Reuse Exhausted Generators
gen = (x for x in range(3))
print(list(gen)) # [0, 1, 2]print(list(gen)) # [] (generator is exhausted)
# Create a new generator insteadgen = (x for x in range(3))print(list(gen)) # [0, 1, 2]Exercises
Exercise 1: Basic Generator Function
Create a generator function called count_up that takes a limit and yields numbers from 0 to limit-1. Read the limit from input and print all generated values.
Basic Generator Function
def count_up(limit): current = 0 while current < limit: yield current current += 1
limit = int(input())for num in count_up(limit): print(num)Exercise 2: Generator Expression
Create a generator expression that generates squares of numbers from 0 to n-1. Read n from input, convert the generator to a list, and print it.
Generator Expression
n = int(input())squares = (x ** 2 for x in range(n))print(list(squares))Exercise 3: Even Numbers Generator
Create a generator function called even_numbers that takes start and end values, and yields all even numbers in that range (inclusive start, exclusive end). Read start and end, then print all even numbers.
Even Numbers Generator
def even_numbers(start, end): for num in range(start, end): if num % 2 == 0: yield num
start = int(input())end = int(input())
for num in even_numbers(start, end): print(num)Exercise 4: Fibonacci Generator
Create a generator function called fibonacci that takes a limit and yields the first limit Fibonacci numbers. Read the limit and print all Fibonacci numbers.
Fibonacci Generator
def fibonacci(limit): prev, curr = 0, 1 count = 0 while count < limit: yield prev prev, curr = curr, prev + curr count += 1
limit = int(input())for num in fibonacci(limit): print(num)Exercise 5: Generator with Condition
Create a generator expression that generates numbers from 0 to n-1, but only includes numbers divisible by 3. Read n from input, convert to list, and print.
Generator with Condition
n = int(input())divisible_by_3 = (x for x in range(n) if x % 3 == 0)print(list(divisible_by_3))