Card 06/ 09
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.
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))class: [0, 2, 4, 6, 8]
generator: [0, 2, 4, 6, 8]
has __next__: True
next twice: 0 2Both 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.
| Concern | Iterator class | Generator |
|---|---|---|
| Where the position lives | Fields on self | Local variables, kept while the function is paused |
| Signalling the end | raise StopIteration | Returning from the function |
| Walking a tree | A stack kept by hand | Recursion with yield from |
| Extra methods | Possible, such as reset or peek | Only 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.
← → move through sections and cards