Python’s for loops can iterate not only over lists and dictionaries but over any “iterable” object. The mechanism working behind this “iterable” concept is the “Iterator.” By implementing specific special methods in your own classes, you can make objects compatible with for loops or the next() function.
This article explains the roles of the two important methods, __iter__ and __next__, and how to implement them.
Difference Between Iterable and Iterator
Before implementing, let’s clarify the terminology.
- Iterable: “An object capable of returning an iterator.”
- It has an
__iter__method. - Examples: list, tuple, dictionary, string.
- It has an
- Iterator: “An object with the ability to produce values one by one.”
- It has a
__next__method to return the next value (or notify the end). - Since it must also be an iterable itself, it also has an
__iter__method (which usually returnsself).
- It has a
Methods Required for Implementation
To make a custom class an iterator, you need to implement the following two special methods. Together, these are called the “Iterator Protocol.”
__iter__(self)Returns the iterator object itself (usuallyself). This allows the object to be recognized as an iterable inforloops.__next__(self)Returns the next element. If there are no more elements to return, it raises aStopIterationexception to notify the end of the iteration.
Implementation Example: Iterator Returning Even Numbers
Let’s create an EvenNumberIterator class that returns even numbers (0, 2, 4…) in order up to a specified limit.
class EvenNumberIterator:
"""
Iterator that returns even numbers up to a specified limit.
"""
def __init__(self, limit):
self.limit = limit
self.current = 0
def __iter__(self):
"""
Returns the iterator itself.
"""
return self
def __next__(self):
"""
Returns the next even number. Ends if it exceeds the limit.
"""
# Check if the current value exceeds the limit
if self.current > self.limit:
# Raise exception to notify the end of iteration
raise StopIteration
# Keep the value to return
value_to_return = self.current
# Update value for the next call (+2)
self.current += 2
return value_to_return
# --- Verification ---
# Create an iterator generating even numbers up to 10
even_gen = EvenNumberIterator(10)
print("--- Calling with next() function ---")
# __next__ is executed each time next() is called
print(next(even_gen)) # 0
print(next(even_gen)) # 2
print(next(even_gen)) # 4
print("\n--- Processing the rest with a loop ---")
# The for loop continues calling next() until StopIteration is raised
for num in even_gen:
print(num)
Output:
--- Calling with next() function ---
0
2
4
--- Processing the rest with a loop ---
6
8
10
The “One-Time Use” Characteristic of Iterators
Once an iterator reaches the end (raises StopIteration), it is “exhausted.” If you try to run the same iterator object in a for loop again, nothing will be output.
# Iterator that has already reached the end
print("--- Attempting reuse ---")
for num in even_gen:
print(num) # Not executed
print("--- End ---")
Output:
--- Attempting reuse ---
--- End ---
If you want to create an object that can be looped over “as many times as you like” (like a list), you need to implement the __iter__ method to create and return a new iterator instance each time.
Summary
- To create a custom iterator, implement
__iter__and__next__. __iter__returnsself.__next__returns the next value and raisesStopIterationwhen finished.- Iterators retain state (current position), so they cannot be reused once exhausted.
- While Generators (
yield) are easier for simple cases, implementing a class allows for complex state management and custom methods.
