Iterator Pattern
The Iterator will commonly contain two methods that perform the following concepts.
- next: returns the next object in the aggregate (collection, object).
- has_next: returns a Boolean indicating if the Iterable is at the end of the iteration or not.
The benefits of using the Iterator pattern are that the client can traverse a collection of aggregates(objects) without needing to understand their internal representations and/or data structures.
Terminology
- Iterator Interface: The Interface for an object to implement.
- Concrete Iterator: (Iterable) The instantiated object that implements the iterator and contains a collection of aggregates.
- Aggregate Interface: An interface for defining an aggregate (object).
- Concrete Aggregate: The object that implements the Aggregate interface.
Iterator Pattern UML Diagram
Source Code
In this concept example, I create 4 objects called Aggregate and group them into a collection.
They are very minimal objects that implement one method that prints a line.
I then create an Iterable and pass in the collection of Aggregates.
I can now traverse the aggregates through the Iterable interface.
./iterator/iterator_concept.py
"""
The Iterator Pattern Concept
https://sbcode.net/python/iterator/#iteratoriterator_conceptpy
"""from abc import ABCMeta, abstractmethodclass IIterator(metaclass=ABCMeta):
"An Iterator Interface"
@staticmethod
@abstractmethod
def has_next():
"Returns Boolean whether at end of collection or not" @staticmethod
@abstractmethod
def next():
"Return the object in collection"class Iterable(IIterator):
"The concrete iterator (iterable)" def __init__(self, aggregates):
self.index = 0
self.aggregates = aggregates def next(self):
if self.index < len(self.aggregates):
aggregate = self.aggregates[self.index]
self.index += 1
return aggregate
raise Exception("AtEndOfIteratorException", "At End of Iterator") def has_next(self):
return self.index < len(self.aggregates)class IAggregate(metaclass=ABCMeta):
"An interface that the aggregates should implement"
@staticmethod
@abstractmethod
def method():
"a method to implement"class Aggregate(IAggregate):
"A concrete object"
@staticmethod
def method():
print("This method has been invoked")# The Client
AGGREGATES = [Aggregate(), Aggregate(), Aggregate(), Aggregate()]
# AGGREGATES is a python list that is already iterable by default.# but we can create own own iterator on top anyway.
ITERABLE = Iterable(AGGREGATES)while ITERABLE.has_next():
ITERABLE.next().method()
Output
python ./iterator/iterator_concept.py
This method has been invoked
This method has been invoked
This method has been invoked
This method has been invoked
Video of the Iterator Pattern
Example Use Case
Visit Iterator — Design Patterns In Python (sbcode.net) for an example use case of the Iterator pattern.
One reason for not using the inbuilt Python data structures that implement iterators already, or using the iter function directly over an existing collection, is in the case when you want to create an object that can dynamically create iterated objects, you want a custom order of objects or an infinite iterator.
The iterator in the use case example will return the next number in the iterator multiplied by 2 modulus 11. It dynamically creates the returned object (number) at runtime.
It has no has_next()
method since the result is modulated by 11, that will loop the results no matter how large the iterator index is. It will also appear to alternate between a series of even numbers and odd numbers.
Also, just to demonstrate that implementing abstract classes and interfaces is not always necessary, this example uses no abstract base classes or interfaces.
Video of an Iterator Pattern use case
Summary
- There are many ways to create Iterators. They are already built into Python and used instead of creating custom classes.
- Use an iterator when you need to traverse over a collection, or you want an object that can output a series of dynamically created objects.
- At minimum, an iterator needs a
next
equivalent method that returns an object. - Optionally you can also create a helper function that indicates whether an iterator is at the end or not. This is useful if you use your iterator in a
while
loop. - Alternatively, use the
sentinel
option of the Pythoniter()
method to indicate the last iteration. Note that the Iterator object needs to be callable. Set the__call__
reference to itsnext
method.
Thankyou for reading my quick tutorial on the Iterator Design Pattern. For more design patterns please visit my series on Design Patterns in Python at https://medium.com/design-patterns-in-python
Sean
Design Patterns In Python (Book)
You can also buy this series in paperback.
Book provides FREE access to online instructional videos.
Design Patterns In Python : ASIN B08XLJ8Z2J