What is an iterable in Python

Iterable is a ubiquitous concept in Python. What exactly makes an object iterable?

What is an iterable in Python

Iterable (iter-able) is an object we can iterate over - we can go over its items one by one.

Python documentation says it is:

An object capable of returning its members one at a time.

It might be:

  • an object containing multiple items.
  • an object we can split into multiple items.
  • an object that generates items. Contains, or can be split into or generate items

We could also say it is an object that allows us to read its values one by one.

When an object is iterable, we can:

  • Use it in for loops.
  • Use the in operator to check if it contains some item.
  • Use it as an input for comprehensions.

The most practical definition is probably this:

If we can use the object in a for loop, it is iterable.

First, let's look at some built-in iterable objects in Python. Then we'll explain what makes an object iterable and how it works under the bonnet.

Built-in iterable objectsPermalink

CollectionsPermalink

Collections objects are containers that are used to store collections of data. We set what is in the collection when we create it, and for mutable collections, we can also add items to them later.

All collections objects are iterable:

  • list (and deque)
  • tuple ( and namedtuple)
  • set ( and frozenset)
# list
my_list = ['one', 'two', 'three']
for i in my_list:
    print(i)

# tuple
my_tuple = ('one', 'two', 'three')
for i in my_tuple:
    print(i)

# set
my_set = {'one', 'two', 'three'}
for i in my_set:
    print(i)

# all 3 above output: 
# one
# two
# three

dict is also iterable, but when we iterate over a dict, we iterate over its keys only:

my_dict = {'a': 1, 'b': 2, 'c': 3}
for i in my_dict:
    print(i)
# outputs: 
# a
# b
# c

Iterating over a dict

Strings, files and generatorsPermalink

Many other objects are iterable, although their purpose is not to store some items for us. These objects contain data that can be split into smaller parts or generate some data that we can get one by one. We do not put things into these objects, but we can still read their content using iteration.

Probably the most common iterable is str - we can iterate over its characters:

s = 'hey'
for i in s:
    print(i)
# h
# e
# y

Another object which is also iterable is a file object (specifically text file object). When iterating over a text file object, each line in the file is one item.

If we had a file lines.txt like this:

line 1
line 2
line 3

Then it would work like this:

with open('lines.txt') as my_file:
    for i in my_file:
        print(i)

# outputs:
# line 1
# line 2
# line 3

Iterating over a file

All generators are also iterable. Generators are objects that yield values.

Say we write a generator function that yields doubles of numbers up to the number provided as an argument:

def doubles(up_to):
    number = 0
    while number < up_to:
        number = number + 1
        yield number * 2

We can then iterate over the values it produces:

for x in doubles(3):
    print(str(x))

# outputs:
# 2
# 4
# 6

What makes an object iterable?Permalink

To iterate over an iterable, Python first needs to get an iterator (iter-ator) for the iterable.

Iterator is an object that does the actual iteration over an iterable. It feeds data from iterable to the outside world one by one. Iter-ator is used by the for loop to get items one by one.

Based on this, we can say that what makes an object iterable is the fact that we can get an iter-ator for it.

There is no iterable without an iterator.

Iterable needs iterator

Ok, so how do we get an iterator for an iterable?

Getting the iteratorPermalink

To get an iterator for an object, Python uses the built-in function iter().

This function takes one argument - an object we want to iterate over.

If the object is iterable, iter() returns an iter-ator for it. If the object is not iterable, it raises TypeError.

We pass to iter() a list of numbers in the following code. We get back a list_iterator object.

>>> numbers = [1, 2, 3]
>>> iter(numbers)
<list_iterator object at 0x10bf13b20>

The following code passes just the number 5 to the iter() function. It raises TypeError :

>>> iter(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

iter() gets iterator or TypeError

How does iter() gets an iterator for an object?Permalink

There are two ways iter() can get an iter-ator for iterable.

  1. Iterable has a method __iter__(self).
    This method should return an iterator for this iterable. In this case, the iterable itself is responsible for returning its iterator. In Python 3, all built-in iterables we discussed above have the __iter__() method.

  2. The second way is that iterable has a method __getitem__(self, index).
    This method returns a value for a given index - if there is no such index, it raises an error. So, in this case, iterable is not providing the iterator. The iter() function needs to find an iterator for the iterable.
    How?
    When an object has __getitem__() method, Python considers it a sequence or map — an object that stores items we can retrieve by their index or key. For these, Python provides several built-in iterators for iteration over sequences, dict and other forms. So iter() will use and return one of those built-in itera-tors. Those iterators will use the __getitem__() method to get items from the iterable, as we'll see later.

(Before Python 3, the __getitem__ was the only way to make things iterable. This way of making objects iterable works mostly for compatibility reasons as all iterables in Python 3 have the __iter__() method.)

The following image shows how the iter() function gets an iter-ator for an iterable:
How iter() works

Creating our own iterablePermalink

Now let's have a quick look at how we can create our own iterable by implementing the __getitem__() method so Python will provide us built-in iterator.

We will look at the __iter__() method later when we discuss iterators in detail.

Here's a sample implementation of an iterable object that has the __getitem__() method:

class ZeroToThreeObject:
    def __getitem__(self, index):
        if index <= 3:
            return index * 2
        else:
            raise IndexError

Our class ZeroToThreeObject is not very useful, but it is iterable. In the __getitem__ method, we check if the parameter index is 3 or less and if so, we return the double of it. Otherwise, we raise IndexError.

Let's now check what happens when we pass our object to the iter() function:

>>> zero_three = ZeroToThreeObject()
>>> iter(zero_three)
<iterator object at 0x10be21810>

We can see that we got iterator from iter() function for our object. It works because our object has __getitem__() method.

So our object is iterable, and we can use it as such:

zero_three = ZeroToThreeObject()

for x in zero_three:
    print(str(x))

# outputs:
# 0
# 2
# 4
# 6

It's worth noting that this object is not only iterable but also a sequence (because it has __getitem__ method - as we discussed before), so we can access its items by index too:

zero_three = ZeroToThreeObject()
zero_three[1]
# outputs: 2

zero_three[3]
#outputs: 6

What iterable isn'tPermalink

Iterable is a very useful concept as it abstracts away many details about a particular object. Many functions take an iterable as a parameter without specifying if it should be a list, string, generator etc.

When a function expects iterable, it expects to be able to iterate over it once. If it expects iterable, it can't expect that object will have a length as, e.g. str or list, nor can it expect it will be able to get items from it based on the index or a key as, e.g. list or dict.

  • Iterable is not required to have a length - it might not work with the len() function.
  • Iterable is not required to support indexing - we might not be able to get the item from it with index.
  • Iterable might even be infinite (usually generators).

The only thing iterable provides is that we can iterate over its items one by one.

SummaryPermalink

An iterable:

  • is an object we can iterate over (its values).
  • can be used in a for loop to get its items.
  • is an object for which we can get an iter-ator.
  • There is no iter-able without an iter-ator.
  • We can use the in operator to check if it contains some value.
  • A lot of built-in objects are iterables: list, set, dict, string, file etc.
  • Generators are also iterable (they generate items).
  • We can make our objects iterable by providing the __iter__() method, which returns an iterator.
  • Or by providing the __getitem__(self, index) method, which returns the value at the given index.

Happy coding!

You might also like

Better Python apps in AWS with stelvio.dev

Deep dives and quick insights into cloud architecture.

    We won't send you spam. Unsubscribe at any time.