stackademic

The leading education platform for anyone with an interest in software development.

The Iterator Protocol

Make objects iterable by implementing __iter__ and __next__

Overview

The iterator protocol is the contract that powers every for loop, comprehension, and in check. An iterable implements __iter__, returning an iterator; an iterator implements __next__, returning successive values and raising StopIteration when exhausted. Understanding this protocol lets you build lazy, memory-efficient sequences and integrate custom types with all of Python's iteration machinery.

Syntax / Usage

Separate the iterable from its iterator so multiple independent iterations work correctly.

class Countdown:
    def __init__(self, start: int) -> None:
        self.start = start

    def __iter__(self) -> "CountdownIterator":
        return CountdownIterator(self.start)

class CountdownIterator:
    def __init__(self, current: int) -> None:
        self.current = current

    def __iter__(self) -> "CountdownIterator":
        return self

    def __next__(self) -> int:
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

for n in Countdown(3):
    print(n)  # 3, 2, 1

iter(obj) calls __iter__ and next(it) calls __next__; for loops do both automatically.

Examples

A lazy, potentially infinite iterator that never materializes a list:

class Fibonacci:
    def __iter__(self):
        a, b = 0, 1
        while True:
            yield a          # a generator method satisfies the protocol
            a, b = b, a + b

fib = iter(Fibonacci())
print([next(fib) for _ in range(7)])  # [0, 1, 1, 2, 3, 5, 8]

Any iterator is also an iterable, so it plugs into standard tools:

import itertools

def evens():
    n = 0
    while True:
        yield n
        n += 2

first_five = list(itertools.islice(evens(), 5))
print(first_five)  # [0, 2, 4, 6, 8]

Common Mistakes

  • Returning self from __iter__ on a stateful object, so a second loop resumes mid-sequence
  • Forgetting to raise StopIteration, causing an infinite loop
  • Confusing iterables (re-iterable) with iterators (single-use, exhaust once)
  • Reusing an exhausted iterator and getting an empty result with no error
  • Reimplementing __next__ by hand when a generator (yield) would be far simpler

See Also

python-generators python-dataclasses python-context-managers