__iter__ and __next__
Overview
The `__iter__` and `__next__` dunder methods form the foundation of Python's iteration protocol. Just like a bookmark in a book, the iterator keeps track of your current page. To support this, your class must implement two methods: - `__iter__(self)`: Initializes or returns the iterator instance itself. Returns an object with a `__next__` method (usually `self`). - `__next__(self)`: Advances the sequence to the next state. Returns the next item, or raises `StopIteration` when the sequence is exhausted. class CountUp: def __init__(self, limit): self.limit = limit self.current = 0 def __iter__(self): return self def __next__(self): if self.current >= self.limit: raise StopIteration self.current += 1 return self.current A common trap is trying to reuse an iterator after it's exhausted. Because `__next__` mutates internal state until it raises `StopIteration`, a second `for` loop will instantly terminate. counter = CountUp(2) list(counter) # [1, 2] list(counter) # [] - Exhausted! This single-pass behavior is a key feature of the protocol, covered fully in `iterator-protocol`.
They allow custom objects to behave like built-in sequences (e.g., lists or strings) in `for` loops, comprehensions, and functions like `sum()` or `list()`.
Where used: Custom data structures, Database cursors, Streaming APIs
Why learn this
- You can make your own classes work seamlessly in `for` loops.
- You will understand exactly how Python's built-in `iter()` and `next()` functions work under the hood.
Code walkthrough
class Counter:
def __init__(self, limit):
self.limit = limit
self.val = 0
def __iter__(self):
return self
def __next__(self):
if self.val >= self.limit:
raise StopIteration
self.val += 1
return self.val
for num in Counter(2):
print(num)
Focus: if self.val >= self.limit: raise StopIteration
Aha moment
class Single:
def __iter__(self):
return self
def __next__(self):
return 42
print(next(iter(Single())))
Prediction: What does this code print?
Common guess: An error, because there is no __init__ method or StopIteration.
It prints 42. You don't actually need state or `StopIteration` if you just want an infinite sequence. `iter()` calls `__iter__`, and `next()` calls `__next__` exactly once.
Common mistakes
- Forgetting StopIteration: If `__next__` doesn't raise `StopIteration`, `for` loops will run infinitely.
- Not returning an iterator in __iter__: If `__iter__` returns something that lacks a `__next__` method (like returning a `list` directly instead of an iterator), iteration will fail with a `TypeError`.
Glossary
- iteration protocol
- A specific set of rules in Python that an object must follow to be used in a loop. Example: implementing `__iter__` and `__next__` fulfills the iteration protocol.
- seamlessly
- Working smoothly and perfectly together without any awkward transitions or extra effort required. Example: a custom class working seamlessly in a standard `for` loop.
Recall questions
- What is the specific responsibility of the `__next__` method?
- What must the `__iter__` method return?
- How does a `for` loop know when to stop iterating over an object?
- What happens if you try to loop over an exhausted iterator a second time?
Understanding checks
What will happen when you run this code?
It will print `1` infinitely.
The `__next__` method never raises `StopIteration`, so the `for` loop has no signal to stop and will continue indefinitely.
This iterator is supposed to immediately stop when looped over, but it crashes with a `TypeError` when used in a `for` loop. What is the bug?
The class is missing the `__iter__` method.
A `for` loop always calls `iter()` on the object first, which expects an `__iter__` method to return an iterator. Since `OneTime` only defines `__next__`, it cannot be iterated over.
Practice tasks
Fix the Infinite Loop
The given `Countdown` iterator correctly implements `__iter__` and `__next__`, but it counts down forever into negative numbers. Modify the `__next__` method to raise `StopIteration` when `self.current` reaches `0`.
Challenge
A Repeating Sequence
Create a `Repeater` class initialized with a single value and a maximum count. It should yield that same value `count` times when iterated over. For example, `Repeater('A', 3)` should yield `'A'`, `'A'`, `'A'` and then stop. Implement this using `__iter__` and `__next__`.
Continue learning
Previous: Everything is an object in Python
Previous: __init__ and __new__
Next: The Iterable Protocol