Card 06/ 09

ComparisonDifficulty: Intermediate2 min

Iterator Classes vs Generators in Python

A function with yield is an iterator, with the position kept for you.

A generator is a Python function that contains yield. Calling it returns an iterator, and each next runs the function until the next yield, then pauses there with all its local variables kept. This card writes one iterator both ways and shows that the generator replaces the whole iterator class.

py
from collections.abc import Iterator


class EvensIterator:
    def __init__(self, limit: int) -> None:
        self._limit = limit
        self._current = 0

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

    def __next__(self) -> int:
        if self._current > self._limit:
            raise StopIteration
        value = self._current
        self._current += 2
        return value


def evens(limit: int) -> Iterator[int]:
    current = 0
    while current <= limit:
        yield current
        current += 2


print("class:    ", list(EvensIterator(8)))
print("generator:", list(evens(8)))
generator = evens(8)
print("has __next__:", hasattr(generator, "__next__"))
print("next twice:", next(generator), next(generator))
OutputRan with python:3.12-slim (Python 3.12.13)
class:     [0, 2, 4, 6, 8]
generator: [0, 2, 4, 6, 8]
has __next__: True
next twice: 0 2
Fourteen lines of class, or five lines of function, with the same output.

Both produce the even numbers from 0 to 8. The class keeps its position in self._current and must raise StopIteration itself. The generator keeps its position in the local variable current, which survives between calls because the function is paused, not finished; and when the function returns, Python raises StopIteration for it. The last two lines show that a generator object has __next__, so it is an iterator in every sense the protocol checks.

The two forms compared
ConcernIterator classGenerator
Where the position livesFields on selfLocal variables, kept while the function is paused
Signalling the endraise StopIterationReturning from the function
Walking a treeA stack kept by handRecursion with yield from
Extra methodsPossible, such as reset or peekOnly send, throw and close

Write a generator by default; it is the usual Python form, and it removes the class. Write an iterator class when the iterator needs methods of its own, such as a peek that looks at the next element without consuming it. Either way the loop that uses it does not change.