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.
- Explain what
fordoes:iter()once, thennext()untilStopIteration - 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
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.
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))
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.
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.
forcallsiter()once, thennext()untilStopIteration;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()orsum(). - Next: generator functions, which write iterators like these with
yieldinstead of a class.