Introduction
In this lesson, we delve into Python's iteration protocol and demonstrate how to implement custom iterator classes. Python's iteration protocol underpins many of the language's most powerful features, including for-loops and list comprehensions. By understanding and utilizing the iteration protocol, you can create more efficient and Pythonic code. This lesson will expand your ability to manipulate data sequences by implementing custom iterators, which can be used to create reusable and specialized data traversing mechanisms.
Understanding the Iteration Protocol
Python's iteration protocol involves two main concepts: iterables and iterators. An iterable is any Python object with an __iter__() method or that supports the Sequence protocol. The __iter__() method returns an iterator.
An iterator in Python is an object with a __next__() method, which returns the next item in the sequence. When no items are left, it raises a StopIteration exception.
How For-Loops Use the Iteration Protocol
When a for-loop begins, it uses the iter() function to convert an iterable into an iterator and then repeatedly calls next() to retrieve items from the iterator until StopIteration is raised. Here’s a quick example:
numbers = [1, 2, 3]
iterator = iter(numbers)
while True:
try:
item = next(iterator)
print(item)
except StopIteration:
break
# Output
1
2
3
Implementing Custom Iterators
To create a custom iterator, you need to define a class with __iter__() and __next__() methods.
Example: Custom Range Iterator
Here's a custom iterator that mimics the behavior of Python's built-in range but allows for floating-point increments:
class FloatRange:
def __init__(self, start, stop, step):
self.start = start
self.stop = stop
self.step = step
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
current = self.current
self.current += self.step
return current
# Usage
for number in FloatRange(0.5, 2.0, 0.3):
print(number)
# Output
0.5
0.8
1.1
1.4
1.7
Handling StopIteration Correctly
The StopIteration exception signals that there are no further items remaining. It is crucial that this is correctly implemented to prevent your custom iterator from entering an infinite loop during iteration.
Differentiating Iterable and Iterator Objects
- Iterable: Can be passed to
iter()to get an iterator object. Common examples include lists, tuples, and sets. - Iterator: Implements both the
__iter__()and__next__()methods. It is the object that does the actual iteration.
Practical Example: Filtering Data with Custom Iterators
Suppose you have a list of numbers and you want to create an iterator that only yields even numbers:
class EvenNumberIterator:
def __init__(self, numbers):
self.numbers = numbers
self.index = 0
def __iter__(self):
return self
def __next__(self):
while self.index < len(self.numbers):
number = self.numbers[self.index]
self.index += 1
if number % 2 == 0:
return number
raise StopIteration
# Usage
numbers = [1, 2, 3, 4, 5, 6]
for even in EvenNumberIterator(numbers):
print(even)
# Output
2
4
6
Summary / Key Takeaways
In this lesson on Iteration Protocol and Custom Iterators, you have learned:
- Iteration Protocol: Understanding the roles of
iter()andnext()in Python iteration. - Iterables vs Iterators: Distinguished between objects that can be iterated over and those that drive the iteration.
- Custom Iterators: How to create and implement
__iter__and__next__methods for custom iterator classes. - StopIteration Handling: Importance of properly signaling iteration completion with
StopIteration. - Practical Applications: Using custom iterators in real-world scenarios, like generating sequences and filtering data.
