1. Custom Iterators
Alright, let's change gears a little, and try our hand at building custom iterators.
2. Iterators
Iterators are a special type of class that allow for a collection of objects or a data stream to be traversed, returning one item at a time. Iterators may feel similar to Python lists or tuples, but they act a bit differently.
Iterator are great for navigating over or transforming an existing collection of data, and are commonly-used when generating new data, such as rolling dice.
Iterators can be looped over using for-loops, or the next function can be used to pull an iterator's next value.
For a class to be considered an iterator, it must implement the iterator protocol.
3. Iterator protocol
The iterator protocol is comprised of two magic methods; iter and next.
For a class to be considered an iterator, both of these methods must be defined.
The iter method is quite basic, and only returns an iterator object. This method will typically be a single line, returning self.
The next method is used to return the next value of an iterator. This is where iteration, transformation, and generation take place.
4. Example iterator
Here's an iterator called CoinFlips. CoinFlips takes a desired number of flips, and uses the random library to flip a coin that many times. A counter is defined when a CoinFlips object is created to track the number of times the coin has already been flipped.
The iter method returns a reference to the class itself, using self. In the next method, self-dot-counter is compared to the number of flips. If the counter is less than self-dot-number_of_flips, the counter will be incremented, and the result of the coin flip will be returned.
5. Using an example iterator
To simulate flipping a coin three times, we'll first create a CoinFlips object called three_flips. We can then use the next function to "flip" a coin. Doing this three times happens to yield the order H, H, T.
6. Looping through an iterator
We can also use a for-loop to traverse the elements of three_flips. This may feel like looping through a list, however, once the first three elements are returned, the for loop won't stop; it will output None over and over, in an infinite loop. Let's fix this.
7. StopIteration
To signal the end of a collection or data stream, next must raise a StopIteration exception. This prevents infinite looping when traversing an iterator, and is easy to handle.
Here, next raises a StopIteration exception if the counter is not less than the desired number of flips, signaling that the last coin has been flipped.
8. Looping through an iterator
After updating our CoinFlips class, using a for loop to iterate through three_flips only flips the coin three times before breaking from the loop. Much better than the infinite loop we saw earlier!
9. Handling StopIteration exceptions
If we still want to use the next function to traverse three_flips, we can do that! Inside a while loop is a try-except block. This block uses next to pull the next element from the iterator.
Remember, if all elements have been traversed, CoinFlips will return a StopIteration exception. This can be caught using "except StopIteration", before printing a final output message and breaking from the loop.
10. Let's practice!
Now that you have the tools, it's time to practice creating your own custom iterators!