Module 14 - Iterators & Generators
Iterators and generators are powerful Python concepts for efficient iteration and lazy evaluation. Understanding them is key to writing memory-efficient, Pythonic code.
1. Understanding Iteration
1.1 Iterables vs Iterators
# Iterable: Object that can be iterated over
my_list = [1, 2, 3] # list is iterable
# Iterator: Object that implements __iter__() and __next__()
my_iter = iter(my_list)
# Manual iteration
print(next(my_iter)) # 1
print(next(my_iter)) # 2
print(next(my_iter)) # 3
# print(next(my_iter)) # StopIteration error
1.2 Creating Custom Iterators
class Counter:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
self.current += 1
return self.current - 1
# Usage
counter = Counter(1, 5)
for num in counter:
print(num) # 1, 2, 3, 4
2. Generators
2.1 Generator Functions
def countdown(n):
"""Generator that counts down from n to 1"""
while n > 0:
yield n
n -= 1
# Usage
for num in countdown(5):
print(num)
# Generators are iterators
gen = countdown(3)
print(next(gen)) # 3
print(next(gen)) # 2
2.2 Generator Expressions
# List comprehension (creates full list in memory)
squares_list = [x**2 for x in range(1000000)]
# Generator expression (lazy evaluation)
squares_gen = (x**2 for x in range(1000000))
# Memory efficient iteration
for square in squares_gen:
if square > 100:
break
Summary
✅ Iterators implement __iter__() and __next__()
✅ Generators use yield for lazy evaluation
✅ Generator expressions save memory
✅ Use generators for large data sets
Practice
- Create a Fibonacci generator
- Build an infinite sequence generator
- Write a generator that reads large files line by line