Python > Advanced Python Concepts > Iterators and Generators > Iterator Protocol (`__iter__`, `__next__`)
Custom Range Iterator
This snippet demonstrates how to create a custom iterator using the iterator protocol (__iter__
and __next__
). We'll build a MyRange
class that mimics the behavior of Python's built-in range()
function but showcasing the explicit iterator implementation.
Code Implementation
The MyRange
class defines a custom range. The __init__
method initializes the start and end values. The __iter__
method returns the iterator object itself (in this case, self
). The __next__
method returns the next value in the sequence. When the end is reached, it raises StopIteration
, signaling the end of the iteration.
class MyRange:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return self # MyRange itself is the iterator
def __next__(self):
if self.start >= self.end:
raise StopIteration
else:
current = self.start
self.start += 1
return current
# Usage:
my_range = MyRange(1, 5)
for num in my_range:
print(num)
Concepts Behind the Snippet
The iterator protocol in Python consists of two methods: __iter__
and __next__
. The __iter__
method must return an iterator object. If the class itself is an iterator, it returns self
. The __next__
method must return the next value in the sequence. When there are no more values, it must raise the StopIteration
exception. This signals to the for
loop or other iterator consumers that the iteration is complete.
Real-Life Use Case
Iterators are valuable when dealing with large datasets or when you need to generate values on demand rather than storing them all in memory at once. For example, imagine reading data from a large file line by line. An iterator can efficiently read and process each line without loading the entire file into memory. Another example is working with infinite sequences, such as generating prime numbers. You can create an iterator that yields prime numbers indefinitely.
Best Practices
__next__
method raises StopIteration
when there are no more items to return.MyRange
example shows.yield
keyword), as they often lead to more concise and readable code.
Interview Tip
Be prepared to explain the iterator protocol and how it enables efficient iteration over sequences. You might be asked to implement a custom iterator or to identify situations where using iterators would be beneficial. Understanding the difference between iterators and iterables is also crucial. An iterable is something you can get an iterator from, while an iterator is the object that actually does the iterating.
When to Use Them
Use iterators when you need to process large datasets efficiently, generate values on demand, or work with potentially infinite sequences. They help avoid loading large amounts of data into memory at once.
Memory Footprint
Iterators are memory-efficient because they generate values one at a time, rather than storing the entire sequence in memory. This is particularly important when dealing with large datasets.
Alternatives
Generators provide a more concise syntax for creating iterators using the yield
keyword. List comprehensions can be used to create lists in a more readable way. Standard looping constructs (for
loops) can also be used, but they may not be as memory-efficient when dealing with large datasets.
Pros
Cons
__iter__
and __next__
methods.
FAQ
-
What is the difference between an iterator and an iterable?
An iterable is an object that can return an iterator. It has an__iter__
method that returns a new iterator object. An iterator is an object that generates values on demand. It has a__next__
method that returns the next value in the sequence or raisesStopIteration
when there are no more values. -
When should I use a generator instead of a custom iterator class?
Use a generator when the iteration logic is relatively simple and can be expressed concisely using theyield
keyword. Generators are often more readable and easier to maintain than custom iterator classes. Use a custom iterator class when you need more control over the iteration process or when you need to implement more complex logic.