runtime
156 / 250
lesson 156 Iterators & Generators · advanced

Iterators: iter(), next() and StopIteration

A for loop asks an object for an iterator with iter(), then calls next() on it until StopIteration says there is nothing left. Knowing that protocol lets you pull values by hand and write classes that work in a for loop.

You'll be able to
  • Explain what for does: iter() once, then next() until StopIteration
  • Tell an iterable (can give many iterators) from an iterator (used up after one pass)
  • Write a class with __iter__ and __next__, and an iterable whose __iter__ returns a fresh iterator
The idea

for fare in fares: works on lists, strings, dictionaries, files and ranges because they all speak the same small protocol. The loop first calls iter(fares) to get an iterator, an object that remembers where it is. Then it calls next() on that iterator, once per pass. When there are no values left, next() raises `StopIteration`, and the loop ends quietly.

A list is iterable: every iter() call gives a new iterator starting at the beginning. An iterator itself is used up after one pass. You can drive the protocol by hand, which is useful for taking just the first value or for treating the first line of a file differently.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · Pulling values by hand
fares = [12.8, 7.2, 31.0]
it = iter(fares)
print(next(it))
print(next(it))
print(next(it))
print(next(it, "no more fares"))
print(list(it))
print(list(fares))
example-2.py · next() past the end
days = iter(["2025-01-15", "2025-01-16"])
next(days)
next(days)
print(next(days))

Your own class works in a for loop once it follows the protocol: `__iter__` returns the iterator (for an iterator, that's self), and `__next__` returns the next value or raises StopIteration. This one hands out rows in batches, the way an upload to an API that accepts at most 100 rows per call would need them.

example-3.py · A class that hands out batches
class Batches:
    """Hands out rows in groups of size, one group per next()."""

    def __init__(self, rows, size):
        self.rows = rows
        self.size = size
        self.position = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.position >= len(self.rows):
            raise StopIteration
        batch = self.rows[self.position:self.position + self.size]
        self.position += self.size
        return batch

trip_ids = [16126, 16127, 16128, 16129, 16130]
for batch in Batches(trip_ids, 2):
    print(batch)
batches = Batches(trip_ids, 2)
print(len(list(batches)), len(list(batches)))

When to use it: call next() yourself when you want just the first item, or want to treat the first item (a header) differently from the rest; write __iter__/__next__ when an object produces a sequence of values on demand. Mistakes to avoid: calling next() without a default where the end is possible; looping over the same iterator twice and expecting the values again; and making a class that should be reusable (a route, a playlist) return self from __iter__, so a second loop gets nothing.

Review
  • for calls iter() once, then next() until StopIteration; next(it, default) avoids the exception.
  • An iterable gives a fresh iterator each time; an iterator (a file, a Batches) is used up after one pass.
  • A class with __iter__ and __next__ works in any for loop, list() or sum().
  • Next: generator functions, which write iterators like these with yield instead of a class.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Iterators: iter(), next() and StopIteration — Iterators & Generators · Runtime