The Iterator Protocol
An object is an iterator if it implements two methods: __iter__() (returns the iterator itself) and __next__() (returns the next value, raises StopIteration when done).
Python
# What a for loop actually does:
my_list = [1, 2, 3]
iterator = iter(my_list) # calls __iter__()
print(next(iterator)) # 1 β calls __next__()
print(next(iterator)) # 2
print(next(iterator)) # 3
# next(iterator) # Raises StopIterationβΆ Output
1
2
3Creating a Custom Iterator
Implement __iter__ and __next__ to make any class iterable.
Python
class Countdown:
"""Counts from n down to 1."""
def __init__(self, start):
self.current = start
def __iter__(self):
return self # The iterator IS the object
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for num in Countdown(5):
print(num, end=" ")βΆ Output
5 4 3 2 1 Iterators vs Generators
Generators are iterators created with yield β much less code for the same result.
Python
# Iterator (verbose)
class EvenNumbers:
def __init__(self, limit):
self.num, self.limit = 0, limit
def __iter__(self): return self
def __next__(self):
if self.num > self.limit: raise StopIteration
val = self.num; self.num += 2; return val
# Generator (same thing, 4 lines)
def even_numbers(limit):
for n in range(0, limit+1, 2):
yield n
print(list(even_numbers(10)))βΆ Output
[0, 2, 4, 6, 8, 10]Ad β 336Γ280
itertools β Powerful Iterator Combinators
The itertools module provides fast, memory-efficient iterator tools.
Python
import itertools
# chain: combine multiple iterables
result = list(itertools.chain([1,2], [3,4], [5]))
print(result) # [1, 2, 3, 4, 5]
# islice: slice any iterator
result = list(itertools.islice(range(100), 5))
print(result) # [0, 1, 2, 3, 4]
# cycle: repeat infinitely
counter = 0
for item in itertools.cycle(["A","B","C"]):
print(item, end=" ")
counter += 1
if counter == 7: breakβΆ Output
[1, 2, 3, 4, 5]
[0, 1, 2, 3, 4]
A B C A B C A