Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Iterables vs. Iterators in Python

Hello! Welcome to your first lesson in our course on asyncio and asynchronous iteration.

Introduction

This course is designed to take you from the foundational principles of iteration in Python to the advanced patterns of asynchronous programming with asyncio. Given your goal of mastering asyncio and async iterators, we'll start by building a rock-solid understanding of their synchronous counterparts.

Today's lesson focuses on the first learning outcome: Differentiating between an iterable and an iterator in Python.

While these concepts might seem basic, a precise understanding of their underlying mechanics—the "iterator protocol"—is the critical prerequisite for grasping how asynchronous iteration works. We will explore what defines each concept, how they interact, and why this distinction is fundamental to Python's design.

Let's begin.

What is an Iterable?

At a high level, an iterable is any Python object that can be "looped over." You use them every day. Common examples include lists, tuples, dictionaries, and strings.

The defining characteristic of an iterable is that it must implement a special method called __iter__(). When a for loop is used on an object, it first calls this __iter__() method to get an iterator, which then handles the actual process of iteration.

To get a clear and practical introduction, please watch the first part of the following video from Corey Schafer.

Python Tutorial: Iterators and Iterables - What Are They and How Do They Work?

This video provides a concise explanation of what makes an object iterable, using a list as a primary example and introducing the __iter__ dunder method.

Watch from 00:56 to 03:17. Focus on how the presence of the __iter__ method is the key feature that defines an iterable.

In short:

  • Iterable: An object capable of returning its members one at a time.
  • Protocol: Must implement the __iter__() method.
  • Purpose: To provide a source of data that can be iterated upon.

What is an Iterator?

If an iterable is the data source, an iterator is the object that actually performs the iteration. It fetches items from the iterable one by one and, crucially, maintains an internal state to remember its current position.

An iterator is defined by the iterator protocol, which requires two methods:

  1. __iter__(): This method returns the iterator object itself. This is what allows an iterator to also be used where an iterable is expected (e.g., in a for loop).
  2. __next__(): This method returns the next item from the data stream. When there are no more items, it raises a StopIteration exception to signal that the iteration is complete.

Continue with the same video to see this distinction explained.

Python Tutorial: Iterators and Iterables - What Are They and How Do They Work?

This next segment defines what an iterator is, highlighting its stateful nature and the role of the __next__ method.

Watch from 03:06 to 04:39. Pay attention to the key difference: an iterator has a __next__ method, whereas a standard iterable like a list does not.

The Process of Iteration

So, how do these two concepts work together?

  1. A for loop (or any iteration context) starts with an iterable (e.g., a list).
  2. It calls the built-in function iter() on the iterable. This function, in turn, calls the iterable's __iter__() method.
  3. The __iter__() method returns a fresh iterator object.
  4. The for loop then repeatedly calls the next() function on this iterator to get each item. The next() function calls the iterator's __next__() method.
  5. This continues until __next__() raises a StopIteration exception, which the for loop catches to terminate the loop gracefully.

This diagram provides a clear visual summary of the process:

Caption: This diagram illustrates the standard iteration workflow. An iterable (the list `x`) is passed to `iter()` to produce an iterator. The `next()` function is then used to retrieve values from the stateful iterator until it is exhausted, at which point a `StopIteration` exception would be raised.

To see this mechanism deconstructed, watch the final recommended segment of the video.

Python Tutorial: Iterators and Iterables - What Are They and How Do They Work?

This part demonstrates the iteration process manually, showing how iter() and next() are used under the hood and how a for loop is essentially a while loop handling the StopIteration exception.

Watch from 04:39 to 09:53. This section is key to understanding the mechanics. Notice how an iterator is a single-use, forward-only object.

Practical Exploration

Let's verify these concepts with a short code exercise. You can run this in a Python interpreter.

# 1. Start with an iterable (a list)
my_list = [10, 20, 30]

# 2. Check its available methods. Note the presence of '__iter__'.
#    You will not find '__next__'.
print("'my_list' (a list) has '__iter__':", hasattr(my_list, '__iter__'))
print("'my_list' (a list) has '__next__':", hasattr(my_list, '__next__'))

# 3. Try to call next() on the iterable directly. This will fail.
try:
    next(my_list)
except TypeError as e:
    print(f"\nCalling next() on my_list failed: {e}")

# 4. Get an iterator from the iterable.
my_iterator = iter(my_list)
print(f"\nType of my_iterator: {type(my_iterator)}")

# 5. Check the iterator's methods. It has both '__iter__' and '__next__'.
print("'my_iterator' has '__iter__':", hasattr(my_iterator, '__iter__'))
print("'my_iterator' has '__next__':", hasattr(my_iterator, '__next__'))

# 6. Now, call next() on the iterator. It works and maintains state.
print("\nCalling next() on the iterator:")
print(next(my_iterator))  # Output: 10
print(next(my_iterator))  # Output: 20

# 7. An important detail: calling iter() on an iterator returns itself.
print("\nIs my_iterator the same object as iter(my_iterator)?", my_iterator is iter(my_iterator))

# 8. Calling iter() on an iterable creates a NEW iterator each time.
print("Is iter(my_list) the same object as iter(my_list)?", iter(my_list) is iter(my_list))

The final two print statements reveal a crucial difference:

  • You can create multiple, independent iterators from a single iterable, allowing you to loop over it multiple times.
  • An iterator, when asked for an iterator, just returns itself. This means it's inherently single-use. Once exhausted, it's empty.

Deeper Dive and Summary

For a comprehensive textual summary and further details, the Real Python article on this topic is excellent. Please focus on the section that directly compares the two concepts.

Iterators and Iterables in Python: Run Efficient Iterations

This article, "Iterators and Iterables in Python" from Real Python, provides an in-depth look at the topic. We will focus on the section that summarizes the key differences in a convenient table.

Please read the section titled "Comparing Iterators vs Iterables". The table in this section is a particularly effective summary of the core distinctions regarding reusability, state, and memory.

Here is a summary of the most critical distinctions:

Feature Iterable Iterator
Primary Role A data source or container. A stateful tool to fetch data from a source.
Protocol Implements __iter__(). Implements __iter__() and __next__().
Reusability Can be iterated over multiple times. Each time produces a new iterator. Can only be iterated over once. It is consumed after use.
State Stateless. It doesn't know where you are in an iteration. Stateful. It keeps track of its current position.
Memory Typically holds all its values in memory (e.g., a list). Accesses values one by one (lazily), making it memory-efficient.
Key Relationship iter(iterable) returns an iterator. iter(iterator) returns self.

The key takeaway is the principle of separation of concerns. The iterable holds the data, and the iterator handles the traversal. This allows for clean, memory-efficient, and universal iteration logic across all of Python's collection types and data streams.

Conclusion

In this lesson, we have established the formal distinction between iterables and iterators in Python.

Key Takeaways:

  • An iterable is an object with an __iter__ method that returns an iterator (e.g., list, str). It is the source of data.
  • An iterator is an object with both an __iter__ method (returning itself) and a __next__ method that retrieves sequential values and raises StopIteration when exhausted. It is a stateful helper that consumes the data.
  • The for loop elegantly combines these, using iter() to get an iterator from an iterable and then calling next() on it until StopIteration occurs.

This distinction is not merely academic. It forms the blueprint for more advanced concepts.

Next Up:
In the next lesson, we will move from understanding to creation. You will learn how to implement your own custom iterator class by defining the __iter__ and __next__ methods yourself. This will give you full control over the iteration process and pave the way for understanding generators and, eventually, their asynchronous counterparts.

Can't find a good explanation? Sign up and we'll make it for you

Sign up