Generators

Learn about generators in Python, generator functions, generator expressions, and how they provide an elegant way to create iterators.

Ali Berro

By Ali Berro

9 min read Section 2
From: Python Fundamentals: From Zero to Hero

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

basic-generator.py
def count_up(limit):
current = 0
while current < limit:
yield current
current += 1
# Usage
for num in count_up(5):
print(num) # 0, 1, 2, 3, 4
# Or manually
gen = count_up(3)
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2

How yield Works

When Python encounters a yield statement:

  1. It returns the value to the caller
  2. It pauses the function’s execution
  3. It saves the function’s state (local variables, execution position)
  4. When next() is called again, execution resumes from where it left off
yield-behavior.py
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 1
print(next(gen)) # After first yield, then 2
print(next(gen)) # After second yield, then 3
# print(next(gen)) # Generator finished, then StopIteration

Generator vs Regular Function

Regular Function

regular-function.py
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 memory
squares = squares_list(1000000) # Uses significant memory

Generator Function

generator-function.py
def squares_generator(n):
for i in range(n):
yield i ** 2 # Yields one value at a time
# Values generated on-demand
squares = squares_generator(1000000) # Uses minimal memory
for square in squares:
if square > 100:
break # Only computes what's needed

Common Generator Patterns

Fibonacci Generator

fibonacci-generator.py
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 numbers
for num in fibonacci(10):
print(num) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Infinite Generator

infinite-generator.py
def natural_numbers():
num = 1
while True:
yield num
num += 1
# Generate natural numbers
gen = natural_numbers()
for i in range(5):
print(next(gen)) # 1, 2, 3, 4, 5

Generator with Multiple Yields

multiple-yields.py
def alternating():
yield "even"
yield "odd"
yield "even"
yield "odd"
for value in alternating():
print(value) # even, odd, even, odd

Generator with Conditions

conditional-generator.py
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, 8

Generator Expressions

Generator expressions are a concise way to create generators, similar to list comprehensions but with parentheses instead of square brackets.

Syntax

generator-expression.py
# 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

generator-expression-examples.py
# Simple generator expression
numbers = (x for x in range(10))
print(list(numbers)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# With condition
evens = (x for x in range(10) if x % 2 == 0)
print(list(evens)) # [0, 2, 4, 6, 8]
# With transformation
squares = (x ** 2 for x in range(5))
print(list(squares)) # [0, 1, 4, 9, 16]
# Nested
pairs = ((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:

generator-send.py
def number_generator():
value = yield
while True:
value = yield value * 2
gen = number_generator()
next(gen) # Start the generator
print(gen.send(5)) # 10
print(gen.send(10)) # 20
print(gen.send(3)) # 6

throw()

Raises an exception inside the generator:

generator-throw.py
def simple_gen():
try:
yield 1
yield 2
except ValueError:
yield "Error caught"
gen = simple_gen()
print(next(gen)) # 1
print(gen.throw(ValueError)) # Error caught

close()

Closes the generator:

generator-close.py
def count():
try:
num = 0
while True:
yield num
num += 1
except GeneratorExit:
print("Generator closed")
gen = count()
print(next(gen)) # 0
print(next(gen)) # 1
gen.close() # Generator closed

Generator Pipelines

Generators can be chained together to create powerful data processing pipelines:

generator-pipeline.py
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 generators
pipeline = even_only(square(numbers()))
print(list(pipeline)) # [0, 4, 16, 36, 64]

Generator Advantages

  1. Memory Efficient: Only produces values when needed
  2. Lazy Evaluation: Computes values on-demand
  3. Clean Code: More readable than custom iterator classes
  4. Infinite Sequences: Can represent infinite sequences
  5. State Preservation: Automatically preserves function state

Memory Comparison

memory-comparison.py
import sys
# List comprehension - stores all values
list_comp = [x ** 2 for x in range(1000000)]
print(sys.getsizeof(list_comp)) # Large size (millions of bytes)
# Generator expression - doesn't store values
gen_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

iterator-class.py
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, 1

Using Generator Function

generator-function-simple.py
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, 1

The generator version is much simpler and more Pythonic!

Practical Examples

Reading Large Files

read-large-file.py
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 file
for line in read_large_file('large_file.txt'):
process(line) # Process one line at a time

Generating Combinations

combinations-generator.py
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

sliding-window.py
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, hon

Don’t Reuse Exhausted Generators

exhausted-generator.py
gen = (x for x in range(3))
print(list(gen)) # [0, 1, 2]
print(list(gen)) # [] (generator is exhausted)
# Create a new generator instead
gen = (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

Checks: 0 times
Answer:
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

Checks: 0 times
Answer:
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

Checks: 0 times
Answer:
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

Checks: 0 times
Answer:
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

Checks: 0 times
Answer:
n = int(input())
divisible_by_3 = (x for x in range(n) if x % 3 == 0)
print(list(divisible_by_3))

Course Progress

Section 54 of 61

Back to Course