Python What Does Iterable Mean

What Are Iterators in Python

Python iterable means that the object can be looped through using a for loop or a while loop.

Common examples of iterables are:

  • Lists
  • Strings
  • Dictionaries
  • Tuples
  • Sets

For example, you can loop through a list of numbers:

numbers = [1, 2, 3]

for number in numbers:
    print(number)

Output:

1
2
3

You can use a similar approach to loop through the characters of a string:

word = "Testing"

for character in word:
    print(character)

Output:

T
e
s
t
i
n
g

If you are a complete beginner, this is enough for you to understand at this point.

If you want to understand more, keep on reading.

Next, we are going to take a look at what makes an iterable and what are iterators.

What Makes an Object Iterable

An iterable object means it implements the __iter__ method. This method returns an iterator that can be used to loop through the iterable.

Let’s create a class called Course:

class Course:
    participants = ["Alice", "Bob", "Charlie"]

Let’s then create a Course object:

course = Course()

Let’s try to loop through the Course object:

for student in course:
    print(student)

Output:

Traceback (most recent call last):
  File "example.py", line 7, in <module>
    for student in course:
TypeError: 'Course' object is not iterable

The error says it all—The Course is not iterable.

Trying to loop through this object is thus meaningless.

But is there a way you could turn the Course into an iterable?

Yes, there is.

To convert the Course into an iterable that returns an iterator, implement the two special methods:

  1. __iter__ method.
  2. __next__ method.

Here is the code:

class Course:
    participants = ["Alice", "Bob", "Charlie"]

    def __iter__(self):
        return iter(self.participants)

    def __next__(self):
        while True:
            try:
                value = next(self)
            except StopIteration:
                break
        return value


course = Course()

for student in course:
    print(student)

Output:

Alice
Bob
Charlie

To understand how this code works, you need to better understand iterators and iterables. Furthermore, you need to learn what the special methods __iter__ and __next__ do.

Iterables and Iterators in Python

To qualify as an iterable, the object has to implement the __iter__() method. Let’s inspect a list of numbers using the built-in dir() method to see if it has one:

numbers = [1,2,3,4,5]
print(dir(numbers))

Output:

A long list of methods an iterable has

You can see the list has the __iter__() method. It is thus an iterable.

For a for loop to work, it calls the __iter__() method of a list. This method returns an iterator. The loop uses this iterator to step through all the values.

An iterator is an object with a state. It remembers where it is during an iteration. Iterators also know how to get the next value. They do this by using the __next__() method which is a method each iterator needs to have.

Let’s retrieve the iterator of the numbers list for inspection:

iter_numbers = iter(numbers)

(This is the same as calling numbers.__iter__() )

And let’s call dir() on this to see what methods it has:

print(dir(iter_numbers))

Result:

A long list of methods an iterator has

There’s the __next__() method that characterizes an iterator.

To recap:

  • A list is an iterable because it has the __iter__() method.
  • The __iter__() method returns an iterator.
  • An iterator has the __next__() method to obtain the next value.

Let’s call the __next__() method on the numbers iterator a bunch of times to see what happens:

>>> numbers = [1,2,3,4,5]
>>> iter_numbers = iter(numbers)
>>> next(iter_numbers)
1
>>> next(iter_numbers)
2
>>> next(iter_numbers)
3
>>> next(iter_numbers)
4
>>> next(iter_numbers)
5

Calling next(iter_numbers) always returns the next number in the numbers list.

This is possible because an iterator is an object with a state. It remembers where it left off when __next__() was called last time.

Now, let’s call next() once more:

>>> next(iter_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> 

Because the list just ran out of values a StopIteration exception is raised. The iterator is exhausted.

The works similar to how a for loop works under the hood:

  • It calls the __iter__() method on a list to retrieve an iterator
  • Then it calls the __next__() until there are no values left.
  • When there are no values left, a StopIteration exception is thrown.
  • The for loop handles the exception for you. Thus you never see it when looping.

Here is how to write a code that mimics the behavior of a for loop:

numbers = [1, 2, 3, 4, 5]

# Get the iterator from numbers list
iter_numbers = iter(numbers)

while True:
    try:
        # Try to get the next value from the iterator and print it
        number = next(iter_numbers)
        print(number)
    # If the iterator has no more values, escape the loop
    except StopIteration:
        break

Output:

1
2
3
4
5

How To Create Iterators and Iterables

You can implement the __iter__() and __next__() methods to any class. In other words, you can build custom iterables and iterators yourself.

Example

I’m sure you’re familiar with the built-in range() function in Python. You can use it like this:

for i in range(4):
    print(i)

Output:

0
1
2
3

Let’s implement a custom iterable that behaves like the range() function:

class RangeValues:
    def __init__(self, start_value, end_value):
        self.current_value = start_value
        self.end_value = end_value
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current_value >= self.end_value:
            raise StopIteration
        value = self.current_value
        self.current_value += 1
        return value

Let’s go through the details to understand what’s going on:

Lines 2–4

  • The __init__() method makes it possible to initialize a RangeValues object with start and end values. For example: RangeValues(0, 10).

Lines 6–7

  • The __iter__() method makes the class iterable. In other words, it is possible to call for i in RangeValues(0,10).
  • The __iter__() method has to return an iterator, that is, an object with the __next__() method. Here you can implement the __next__() method into this class. Thus, it’s possible for the __iter__() method to return the class itself because it’s an iterator.

Lines 9–14

  • The __next__() method is responsible for going through the values from start to end.
  • It raises a StopIteration exception when it reaches the end of the range.
  • If the iterator hasn’t reached the end yet, it continues returning the current value (and increments it for the next round).

Let’s finally test the RangeValues class:

for i in RangeValues(1,5):
    print(i)

Output:

1
2
3
4

It works like a charm!

Generators—Readable Iterators in Python

If you take a look at the above example of RangeValues class, you see it’s daunting to read.

Luckily, Python provides you with generators.

A generator is an iterator whose implementation is easier to read. This is because a generator lets you omit the implementation of __iter__() and __next__() methods.

Because a generator is an iterator, it doesn’t return a single value. Instead, it yields values one at a time and keeps track of the current state.

For example, let’s turn the RangeValues class from the earlier example into a generator to make it more readable:

def range_values(start, end):
    current = start
    while current < end:
        yield current
        current += 1

Let’s test the function:

for i in range_values(0,5):
    print(i)

Output:

0
1
2
3
4

The range_values works exactly like the RangeValues class but the implementation is way cleaner.

Infinite Stream of Elements with Generators

Iterators only care about the current value and how to get the next one. It’s thus possible to create an infinite stream of values because you don’t need to store them anywhere.

Example

Let’s create an infinite iterator that produces all the numbers after a starting value. Let’s use a generator function to keep it readable:

def infinite_values(start):
    current = start
    while True:
        yield current
        current += 1

(If you want to see how this could be done with a class, here it is.)

This iterator produces values from start to infinity.

Let’s run it. (Warning: An infinite loop):

infinite_nums = infinite_values(0)

for num in infinite_nums:
    print(num)

Output:

0
1
2
3
4
5
.
.
.

Syntactically it looks as if the infinite_nums really was an infinite list of numbers after 0.

In reality, it’s nothing but an iterator that stores the current value and knows how to get the next one.

Why Are Iterators and Iterables Useful

Iterators and iterables are memory efficient because they make it possible to store only the current value and know how to get the next one.

Imagine you have a file that contains tens of billions of items, such as passwords, and you want to loop through them. There’s no way you can store that big of a number of items into a list or a tuple.

This is where iterators come in.

You can access the elements one by one without storing them in the memory of your program. Better yet, syntactically it looks as if you were really reading the whole list stored in memory all at once.

Here is the code:

numbers = [1,2,3,4,5]

# Get the iterator from numbers list
iter_numbers = iter(numbers)

while True:
    try:
        # Try to get the next value from the iterator and print it
        number = next(iter_numbers)
        print(number)
    # If the iterator has no more values, escape the loop
    except StopIteration:
        break

Output:

1
2
3
4
5

Conclusion

Today you learned what does iterable mean in Python.

An iterable is something that can be looped over in Python.

On a low level, an iterable is an object that implements the __iter__() method which returns an iterator.

An iterator is an object with a state. It remembers where it’s at during an iteration. To qualify as an iterator, an object must implement the __next__() method for obtaining the next value in the iteration process.

Thanks for reading. Happy coding!

Further Reading

Python Tips and Tricks

Share on facebook
Share on twitter
Share on linkedin

Leave a Comment

Your email address will not be published.