Iterators & the Iterator Protocol
Iteration is everywhere in Python - for loops, comprehensions, unpacking, and sum. Objects participate by implementing the iterator protocol or delegating to generators.
Search across all documentation pages
Iteration is everywhere in Python - for loops, comprehensions, unpacking, and sum. Objects participate by implementing the iterator protocol or delegating to generators.
class Countdown:
def __init__(self, start: int) -> None:
self.current = start
def __iter__(self):
return self
def __next__(self) -> int:
if self.current < 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for n in Countdown(3):
print(n)When to reach for this:
itertools and for loopsfrom collections.abc import Iterable, Iterator
class CSVLines:
def __init__(self, lines: Iterable[str]) -> None:
self._lines = iter(lines)
def __iter__(self) -> Iterator[list[str]]:
for raw in self._lines:
yield [cell.strip() for cell in raw.split(",")]
def take(iterator: Iterator[int], n: int) -> list[int]:
result: list[int] = []
for _ in range(n):
try:
result.append(next(iterator))
except StopIteration:
break
return result
if __name__ == "__main__":
rows = CSVLines(["a,b", "c,d"])
for row in rows:
print(row)
print(take(iter(range(10)), 3))What this demonstrates:
__iter__ (generator is iterator)iter() obtains iterator; next() pulls items until StopIterationtake manually consumes iterator with bounded read__iter__ returning iterator.__iter__ (returns self) and __next__.for.yield builds iterator automatically.| Object | Required methods |
|---|---|
| Iterable | __iter__ |
| Iterator | __iter__, __next__ |
# iter with sentinel for binary chunks
with open("data.bin", "rb") as fh:
for chunk in iter(lambda: fh.read(4096), b""):
process(chunk)for. Fix: Call iter() again only on iterables.return; in __next__ raise StopIteration.__iter__.take/islice.| Alternative | Use When | Don't Use When |
|---|---|---|
| Generator function | Most custom iteration | Need class state beyond yield |
itertools | Standard iterator algebra | One-off simple loop |
list materialize | Need reuse/random access | Streams too large |
| async iterator | Async for with I/O | Sync-only pipeline |
Iterable produces iterators. Iterator yields items once until exhausted.
Signals end of iteration to for loop machinery - not meant for general flow control in app code.
Only if object is iterable returning fresh iterator each iter() call. Generators exhausted after one pass.
it = iter(obj); while True: try: x = next(it) ... except StopIteration: break
Repeatedly calls callable until sentinel - useful for stream reads.
Structural isinstance check - custom classes can register or implement __iter__.
Generators faster to write; classes when complex state or multiple methods needed.
Built-ins return iterators - consume once, memory efficient.
__aiter__/__anext__ with async for - see asyncio section.
list(iterator) materialize expected; assert StopIteration on extra next calls.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026