# Iterables, Iteration and iterating through your custom Python Objects
Image Courtesy of istock
This tutorial will aim to help you understand what iterables and iterators are, and their relationship with each other. Secondary to this, understanding how a Python for loop works under the hood will ultimately help in designing a user-defined custom-object that can be iterated over.
An iterable is an object capable of returning its members one by one. Simply put, an iterable is anything that you can loop over using a for loop in Python. Sequences are a very common type of iterable. Examples of built-in sequence types include lists, strings, and tuples.
An iterator is an object representing a stream of data. You can create an iterator object by implementing the iter built-in function to an iterable.
An iterator can be used to manually loop over the items in the iterable. The repeated passing of the iterator to the built-in next function returns successive items in the stream. When the item is consumed from the iterator, it is gone, and eventually, when no more data is available to retrieve, a StopIteration exception is raised.
# Understanding the Python for Loop
Central to developing knowledge on iterables and iterators is understanding how a Python for loop works under the hood. To best illustrate this, lets define a function that can take any iterable, and loop through it without using a for loop.
Our function needs to be able to achieve the following:
· Create an iterator from an iterable
· Repeatedly retrieve the next item from the iterator
· Execute any intended action
· Raise a StopIteration exception when there are no more items to retrieve.
Under the hood, an iterable is being converted into an iterator in a Python for loop.
Our custom function first converts any iterable to an iterator. In the while loop, we then get the next item from the iterator, and execute any action on this item. In this case, I have chosen to write a function to raise the number in the iterator by the power of 2, but any action can be taken, for example, we can even choose to simply print out the numbers in our container or collection.
All forms of looping over iterables in Python work in this way.
# Key Definitions
To better differentiate an iterable from an iterator, it can be helpful to further refine their definitions, and note their differences. Iterators cannot be indexed /sliced(as they can be infinitely long). In addition, unlike iterables, they do not they have a length. In the example below, attempting to get the length of the iterator object, my_iter_list raises a TypeError exception.
> An iterable is something you can loop over.
> An iterator is an object representing a stream of data. It does the iterating over an iterable.
A nice and concise definition for iterators, sourced from StackOverflow, whilst researching for this article, is the following:
iteratoris a more general concept: any object whose class has a
__next__in Python 3) and an
__iter__method that does
Iterators permit users to work with and create lazy iterables. Lazy iterables do not do any work until we ask them for their next item. This feature can help us deal with infinitely long iterables which cannot fit into memory. This is called lazy evaluation and can help save both memory and CPU time.
# The iterator Protocol
As discussed above, the iterator objects are required to support the following 2 methods, which combined, comprise the Python iterator protocol:
The dunder/magic iter method:
Return the iterator object itself. This is required to allow both containers (also called collections) and iterators to be used with the
The dunder/magic next method:
Return the next item from the container. If there are no more items, raise the StopIteration exception.
We may want to create a custom iterator. In order to do that, we need a class that has __init__, __next__, and __iter__ methods defined.
First, lets define a custom class called CustomIterTeams. This class has no in-built iterable behaviour, but we can implement code in our class to make our custom user-defined object behave like an iterable.
There are two ways to get a custom user-defined object to behave like an iterable. The first way involves defining two dunder or magic methods, namely __iter__() and __next__(). The dunder iter method simply needs to return the object itself. This is because, when we write a for loop, this will be the object we intend to iterate over. This iter method returns an iterator.
Under the hood, Python’s for loop use iterators.
Our custom object is now an iterator, and can work with the dunder next method to return successive items in the stream. These two methods work together to enable the iterator protocol.
In the __init__constructor, we set the index in the object with a value of -1. When the next method is called, i.e. as happens during the first iteration in a for loop for example, the index’s value is incremented by 1. We then check to see if the index value is greater than the length of the list of teams that the user decided to add when the object was first created. If the index is less than the length of the teams, we simply return the team with the in-range index from the list of teams.
Once the index is either the same or greater than the length of the team list, we reset the index back to -1 once more (as it was originally set in the init constructor), and raise a StopIteration exception.
The user now has the ability to iterate through the teams created. The CustomIterTeams object, prem_teams is now an iterator, that we can iterate through.
The index is deliberately re-set to its original value once the index reaches the length of the list, before a StopIteration exception is raised. This feature is implemented in order for the user to perform multiple iterations of the object if they want to in the same session, as shown in the python prompt shown below.
We can also now reverse the ordering of the teams, by simply implementing the dunder reserved method.
# A simpler way to define a custom iterable type
It is not necessary to define a dunder next method in order to make a user-defined object iterable. Rather, we get just get the dunder iter method to return a generator, that loops through our teams. Every generator is an iterator. Generators have a built in next method, so there is no requirement to implement the next method in your custom python class.
The github gist for this code snippet can be found here, and is shown below:
Iteration can be achieved in your custom defined classes either by including both the iter and next methods, or simply returning a generator in the iter method. The choice is up to programmer, but whilst the iter and next method implementation is a little longer, more finely defined behaviour can be added. https://towardsdatascience.com/how-to-loop-through-your-own-objects-in-python-1609c81e11ff