Feb
18

# Iterators

I got the Gang of Four book on design patterns a couple of years ago and it was way beyond me (Alex Martelli at Google doesn’t suggest it as an introduction, I’ve since learned). I still haven’t mastered it, but every now and then I do find a useful idea.

Let me introduce you to the object-oriented idea of iterators.


Concept

The iterators I’ll demonstrate here are pretty simple, and could easy be done using simple expressions (for instance, k++ or k += 2) but more complex iterators would be difficult using this method. Sometimes it is necessary to have an entire routine determine the next value.

If you’re not sure what I mean, read on. It’ll become clearer with some examples.

Iterators as a Class

Suppose we want to re-invent the wheel for k += 1. Here’s a class that does just that.

class CountingIterator:
    def __init__(self):
        # Initialize the value we'll be using.
        self._k = 0

    def next(self):
        # Return the next value when this method is called.
        self._k += 1
        return self._k

This class would get used like this:

>>> simple_iterator = CountingIterator()
>>> simple_iterator.next()
1
>>> simple_iterator.next()
2
>>> simple_iterator.next()
3

It seems simple, I know, but this structure lets more complicated iterations be done. Suppose you don’t want to count indefinitely. Instead, maybe, you want to loop indefinitely. For instance, let’s make an iterator that will cycle through a set of colors.

class LoopingIterator:
    def __init__(self, color_list):
        self._color_list = color_list
        self._k = -1

    def next(self):
        self._k += 1
        self._k %= len(self._color_list)
        return self._color_list[self._k]

This is a little more complex, but it’s not too bad. The __init__ method initializes a list of colors and sets _k to -1. Then, when next() is called, _k is incremented. The %= operator finds the remainder when the value on the left is divided by the value on the right, then assigns that value to the variable on the left. In other words, we don’t want the third line in next() to try and get an out of bounds index in the list, so we use the mod operator to reduce the value of _k.

This iterator would be used like this.

>>> colors_iterator = LoopingIterator(['red','green','blue'])
>>> colors_iterator.next()
red
>>> colors_iterator.next()
green
>>> colors_iterator.next()
blue
>>> colors_iterator.next()
red

Clearly the next() method can be written for whatever purpose suits your needs. These two examples are very simple, but more complex iterators can be made using this method. In fact, other methods can be added such as previous() or skip(). That is what makes the iterator design pattern so powerful.

In Python, custom iterators like this can be made for use in for loops. In other words, rather than simply iterating over entries in a list, something more interesting can be made. I’ll cover that, though, in another post.

Iterators in Python

The examples above can be achieved in Python using the yield statement in a function definition. Some of the advanced functions of iterators are not available (such as the hypothetical previous() and skip()), but it works quite well for the next() method.

Rather than declaring a class to do this, we’ll only have a function, but it will do the same things. Here it is for the first example.

def CountingIterator():
    k = 0
    while True:
        k += 1
        yield k

When the iterator is initialized, it sets k equal to 0, then goes into an infinite loop. When the next() method is called, it returns the value of k at the yield statement, then goes through another iteration. When it reaches yield again, it waits until next() is called, then returns that value and goes through another iteration.

The second example from above looks like this.

def LoopingIterator(list):
    k = -1
    while True:
        k += 1
        k %= len(list)
        yield list[k]

That’s all there is to it.

Conclusion

Iterators are powerful things, especially in Python where there is a built-in __iter__() method. They can be very useful in for loops if one knows how to use them, but I’ll wait to think of a good tutorial example before I go into that.

2 Comments to “# Iterators”

  • In this example:

    
        def next(self):
            self._k += 1
            self._k %= len(self._color_list)
            return self._color_list[k]
    
    

    Shouldn’t the line
    return self._color_list[k]
    be
    return self._color_list[self._k]
    instead?

  • Fixed. Thanks.

Leave a comment