What are Iterable, Iterator, and Iteration in Python?

Python Tutorials

Summary: In this tutorial, we will learn what iterable, iterator, and iteration are in Python and how they differ from each other with the help of examples.

When we fetch one item of something one after another it is called iteration. For example, when we use for loop on a list or string to go over its items, it is iteration.

An iterator is an object in Python that has a stream of data and __next__ method to return the next value in the iteration.

Whereas iterable is a Python object that has a __iter__ method which returns an iterator object or a __getitem__ method which implements efficient element access using integer indices.

To do the iteration we use the for loop (or other constructs such as while) with an iterable object that returns an iterator.

Consider the following code as an example:

>>> l = [1, 2, 3, 4, 5]
>>> for x in l:
...    print(x*2, end=' ')
2 4 6 8 10

Here, the list l is an iterable object. When we use it with for statement, the for statement automatically calls the __iter__ method on l and creates a temporary unnamed variable to hold the returned iterator object for the duration of the loop.

The for statement then uses the __next__ method on this iterator object to do the iteration i.e. fetch one item after another.

We cannot directly use constructs such as list, while, etc, with an iterator object. We use them with an iterable that returns an iterator.

It is then the iterator object that is responsible for returning the successive item from the stream of data.

To get this clear, let’s try writing a custom iterator and iterable in Python.

Custom Iterator and Iterable in Python

An iterator must have the __next__ method to return successive items from the stream of data. So, we create a class and define __next__ method to create a custom iterator as follows:

class MyIterator:
    def __init__(self):
        self.index = 0
        
    def __next__(self):
        self.index += 1
        return self.index

Here, we have successfully created a custom iterator that returns the next natural number on each successive call.

If we use the next() method on the iterator object, we see that __next__ returns the successive natural number on each call.

>>> iterator = MyIterator()
>>>
>>> next(iterator)
1
>>> next(iterator)
2
>>> next(iterator)
3
>>> next(iterator)
4

The next() is a buit-in function in Python that retrieve the next item from the iterator by calling its __next__ method.

The above custom iterator produces an infinite stream of natural numbers. To be finite, it must raise the StopIteration exception.

class MyIterator:
    def __init__(self):
        self.index = 0
        
    def __next__(self):
        if self.index >= 5:
            raise StopIteration
            
        self.index += 1
        return self.index

Now if we use next() on the iterator more than 5 times, we get the StopIteration exception.

>>> iterator = MyIterator()
>>>
>>> next(iterator)
1
>>> next(iterator)
2
>>> next(iterator)
3
>>> next(iterator)
4
>>> next(iterator)
5
>>> next(iterator)
Traceback (most recent call last):
    StopIteration

This exception is used by the constructs such as for, while, list, etc, to stop the iteration.

However as discussed, we cannot directly use the iterator object with the for statement because for and other constructs in Python accept an iterable that return an iterator.

>>> for x in iterator:
...     print(x)
TypeError: 'MyIterator' object is not iterable

To make this work, we have to return this iterator object from an iterable.

As we know iterable is an object that has __iter__ method which returns an iterator object, so we create a class with __iter__ method which returns the above iterator.

class MyIterable:
    def __iter__(self):
        self.index = 0
        return self
        
    def __next__(self):
        if self.index >= 5:
            raise StopIteration
            
        self.index += 1
        return self.index

Now if we use for statement with the object of this class, we see the iteration happens without any error.

>>> iterable = MyIterable()
>>> for x in iterable:
...     print(x, end=' ')
1 2 3 4 5

The MyIterable class is similar to the MyIterator class, the only difference is that it has an additional __iter__ method that returns the ‘self’ object i.e. iterator object.

It can be confusing how ‘self‘ is an iterator object, because we have defined __iter__ in the class to make it an iterable.

It is because in Python we cannot use iterator without having __iter__ method. This makes the iterator an iterable in Python.

Is an iterator always iterable?

Every iterator in Python is iterable because an iterator is required to have an __iter__ method to return the iterator object.

However, the vice versa is not true. We can create iterable without the iterator by using the __getitem__ method in Python.

For example in the following custom iterable class, we have not defined an __iter__ method to return an iterator object, but have defined a __getitem__ method to return the item at the given integer index needed by the for statement:

class MyIterable:
    def __getitem__(self, index):
        if index >= 5:
            raise StopIteration
    
        return index*2

If we do the iteration on the object of this class we will get a sequence of integer indices multiplied by 2 as the output.

>>> iterable = MyIterable()
>>> for x in iterable:
...     print(x, end=' ')
0 2 4 6 8

In summary, the iterable is a Python object that returns an iterator for constructs such as for, while, list, etc, to do the iteration in Python. An iterable has the __iter__ method to return the iterator object and an iterator has the __next__ method to return the successive items in the stream.

Leave a Reply

Your email address will not be published. Required fields are marked *