Skip to main content

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

  1. Create a Fibonacci generator
  2. Build an infinite sequence generator
  3. Write a generator that reads large files line by line