Iterables, Iterators, and the Iterator Protocol
Python · Core Language · Run with python3
Explanation
Python's `for` loop works on any *iterable* — an object that implements `__iter__()` returning an *iterator*. An iterator implements `__next__()` raising `StopIteration` when exhausted. Built-ins like `list`, `dict`, `str`, `range`, files, and generators are all iterables. `iter(obj)` and `next(obj)` are the protocol functions. Understanding this protocol explains how `for`, comprehensions, `zip`, `map`, `filter`, and `itertools` all interoperate.
Real-World Scenario
A custom `DatabaseCursor` class that lazily fetches rows in batches from a remote database can implement the iterator protocol. Callers use it with a normal `for` loop — they don't need to know about the batching. ```python class CountUp: """Iterator that counts from start to stop.""" def __init__(self, start: int, stop: int): self._current = start self._stop = stop def __iter__(self): return self # Iterator is its own iterable def __next__(self): if self._current >= self._stop: raise StopIteration val = self._current self._current += 1 return val for n in CountUp(1, 6): print(n, end=" ") # 1 2 3 4 5 # Equivalent generator expression gen = (n for n in range(1, 6)) print(list(gen)) # [1, 2, 3, 4, 5] ```
Quick Snippet
class CountUp:
"""Iterator that counts from start to stop."""
def __init__(self, start: int, stop: int):
self._current = start
self._stop = stop
def __iter__(self):
return self # Iterator is its own iterable
def __next__(self):
if self._current >= self._stop:
raise StopIteration
val = self._current
self._current += 1
return val
for n in CountUp(1, 6):
print(n, end=" ") # 1 2 3 4 5
# Equivalent generator expression
gen = (n for n in range(1, 6))
print(list(gen)) # [1, 2, 3, 4, 5]Full Example
# Compile: python3 iterators.py
# Output:
# 1 2 3 4 5
# [1, 2, 3, 4, 5]
class CountUp:
def __init__(self, start: int, stop: int):
self._current = start
self._stop = stop
def __iter__(self):
return self
def __next__(self):
if self._current >= self._stop:
raise StopIteration
val = self._current
self._current += 1
return val
for n in CountUp(1, 6):
print(n, end=" ")
print()
# Generator expression — equivalent, shorter
gen = (n for n in range(1, 6))
print(list(gen))
# Protocol functions
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
print(next(it, "done")) # 30
print(next(it, "done")) # done (default, no StopIteration)
# iter() with sentinel
import io
f = io.StringIO("a\nb\n\n")
lines = list(iter(f.readline, "")) # reads until empty string
print(lines)
python3 iterators.py