Python tutorials > Advanced Python Concepts > Iterators and Generators > How to create custom iterators?
How to create custom iterators?
Iterators are a fundamental concept in Python, enabling efficient traversal through data structures. While Python provides built-in iterators for common types like lists and dictionaries, you can also create your own custom iterators to handle more specific or complex iteration scenarios. This tutorial will guide you through the process of creating custom iterators in Python.
Understanding Iterators and Iterables
Before diving into custom iterators, let's clarify the core concepts:
__iter__()
method which returns an iterator. Examples include lists, tuples, strings, and dictionaries.__iter__()
(which returns itself) and __next__()
(which returns the next value). When there are no more values, it raises a StopIteration
exception.
Basic Structure of a Custom Iterator
This example shows the basic structure. The MyIterator
class initializes with the data to iterate over. The __iter__()
method returns the iterator object itself. The __next__()
method returns the next item in the sequence. When the end of the sequence is reached, it raises a StopIteration
exception. The example usage then demonstrates how to iterate through it using a for
loop, implicitly calling __iter__()
and __next__()
.
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.data):
value = self.data[self.index]
self.index += 1
return value
else:
raise StopIteration
# Example Usage
my_list = [1, 2, 3, 4, 5]
my_iterator = MyIterator(my_list)
for item in my_iterator:
print(item)
Example: Creating an Iterator for Even Numbers
This example creates an iterator that yields even numbers up to a specified maximum value. The __init__
method initializes the maximum number and the starting number (0). The __next__
method increments the current number by 2 and returns it if it's less than or equal to the maximum number. Otherwise, it raises StopIteration
.
class EvenNumberIterator:
def __init__(self, max_number):
self.max_number = max_number
self.current_number = 0
def __iter__(self):
return self
def __next__(self):
self.current_number += 2
if self.current_number <= self.max_number:
return self.current_number
else:
raise StopIteration
# Example Usage
even_numbers = EvenNumberIterator(10)
for num in even_numbers:
print(num)
Concepts Behind the Snippet
The core idea is to encapsulate the state of the iteration (e.g., the current index or value) within the iterator object. The __next__()
method is responsible for updating this state and returning the next value, or signaling the end of the iteration.
Real-Life Use Case Section
Custom iterators are useful when dealing with large datasets that don't fit into memory. For example, you could create an iterator to read data from a large file line by line or chunk by chunk, processing each unit without loading the entire file into memory. Another use case is iterating over a complex data structure where the standard iteration mechanisms are not sufficient, such as a graph or tree.
Best Practices
StopIteration
: Always raise the StopIteration
exception when the iterator reaches the end of the sequence.
Interview Tip
When discussing iterators in an interview, emphasize your understanding of the iterator protocol (__iter__()
and __next__()
methods) and the benefits of using iterators for memory efficiency and lazy evaluation. Be prepared to explain how you would design a custom iterator for a specific problem.
When to Use Them
Use custom iterators when you need to iterate over a sequence in a non-standard way, when dealing with large datasets that cannot fit into memory, or when you want to implement lazy evaluation.
Memory Footprint
Custom iterators, like all iterators, are memory-efficient because they only generate the next value when it is requested. This is in contrast to creating a list of all values upfront, which can consume a significant amount of memory for large datasets.
Alternatives
Generators (using the yield
keyword) provide a more concise way to create iterators. List comprehensions can also be used for simple iteration scenarios, but they create a list in memory, which may not be suitable for large datasets. Standard Python libraries such as itertools
offer a wide range of iterator building blocks.
Pros
Cons
FAQ
-
What is the difference between an iterable and an iterator?
An iterable is an object that can return an iterator. An iterator is an object that produces the next value in a sequence.
-
Why use iterators instead of lists?
Iterators are more memory-efficient than lists because they generate values on demand, while lists store all values in memory.
-
How do I know when to raise StopIteration?
Raise
StopIteration
when the iterator has reached the end of the sequence and there are no more values to return.