Iterators & Generators
Iterator Protocol
When you write for item in my_object, Python needs to know how to get items from your object. The iterator protocol defines this contract - implement iter and next and your custom classes work seamlessly with for loops, list(), and all iteration tools.
Infinite Iterators
basic.py
# Basic iterator
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
value = self.current
self.current += 1
return value
# Use the iterator
counter =
for num in counter:
print(num)
# Basic iterator
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
value = self.current
self.current += 1
return value
# Use the iterator
counter =
for num in counter:
print(num)
# Basic iterator
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
value = self.current
self.current += 1
return value
# Use the iterator
counter =
for num in counter:
print(num)
manual.py
# Using iter() and next()
data = [10, 20, 30]
# Get iterator manually
it = iter(data) # calls data.__iter__()
# Get values manually
print(next(it)) # calls it.__next__() -> 10
print(next(it)) # -> 20
print(next(it)) # -> 30
# StopIteration on exhaustion
try:
print(next(it)) # iterator exhausted
except StopIteration:
print("Iterator exhausted")
iterable_vs_iterator.py
# Iterable vs iterator
class NumberList:
"""Iterable (not an iterator)"""
def __init__(self, numbers):
self.numbers = numbers
def __iter__(self):
return NumberIterator(self.numbers)
class NumberIterator:
"""Iterator for NumberList"""
def __init__(self, numbers):
self.numbers = numbers
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.numbers):
raise StopIteration
value = self.numbers[self.index]
self.index += 1
return value
# Use the iterable multiple times
numbers = NumberList([10, 20, 30])
print("First iteration:")
for n in numbers:
print(n)
print("\nSecond iteration:")
for n in numbers:
print(n)
custom_range.py
# Custom range-like iterator
class MyRange:
def __init__(self, start, stop, step=1):
self.current = start
self.stop = stop
self.step = step
def __iter__(self):
return self
def __next__(self):
if (self.step > 0 and self.current >= self.stop) or (
self.step < 0 and self.current <= self.stop
):
raise StopIteration
value = self.current
self.current += self.step
return value
# Use like built-in range
print("Forward:")
for i in MyRange(0, 10, 2):
print(i, end=" ")
print("\n\nBackward:")
for i in MyRange(10, 0, -2):
print(i, end=" ")
infinite.py
# Infinite iterator
class InfiniteCounter:
def __init__(self, start=0):
self.current = start
def __iter__(self):
return self
def __next__(self):
value = self.current
self.current += 1
return value
# Infinite iterator (use break to stop)
counter = InfiniteCounter(1)
for num in counter:
print(num)
if num >= 5:
break
Why It Matters
Understanding the iterator protocol helps you:
- Create custom iterables
- Understand how
forloops work - Debug iteration issues
- Implement lazy evaluation
iterator protocol - the __iter__() and __next__() methods that let objects work with for loops
iterable - an object with __iter__() that returns an iterator; can be iterated multiple times
StopIteration - the exception that signals the end of iteration
Exercise: practical.py
Create a circular buffer iterator that loops through elements