3. Iterators#
3.1. Introduction#
Iterating means doing something repeatedly [iteration, n.d.], and in software development, it typically refers to accessing data elements one by one. An iterator is an object that enables iteration over a collection or stream of data [Python Software Foundation, 2025].
Iterator
Iterators provide a mechanism to access items sequentially (one-by-one) from an iterable source of data, such as a stream or collection (e.g., a dictionary or list).
This raises an important question: what qualifies as an iterable source of data?
Iterable source of data
An iterable source of data is any source from which elements can be retrieved sequentially. Syntactically, it is an object that implements the __iter__ method (see Iterable sources of data).
Iterators are most commonly encountered in for loops. By convention, when looping over an object:
1for item in my_collection:
2 print(item)
Python internally transforms this into the following equivalent code:
1iterator = iter(my_collection)
2while True:
3 try:
4 item = next(iterator)
5 print(item)
6 except StopIteration:
7 break
This transformation demonstrates how Python uses iterators under the hood. To access successive values from a collection, we use an iterator object that must comply with the iterator protocol (see Iterator protocol).
This logic can be seen in the example below:
Iterating through data accessible via iterator
next(iterator) points to and retrieves the next value in sequence. The original collection remains unchanged.
3.2. Iterating over an object#
An iterator object has a straightforward requirement: it must implement the dunder __next__ method with the following signature:
1from typing import Any
2
3class MyDummyIterator:
4
5 def __next__(self) -> Any:
6 pass
This is an argumentless[1] method whose purpose is to produce successive elements from a collection or stream of data. When the iterator is exhausted, it must raise a StopIteration exception.
Exhausted iterator
An exhausted iterator has no more data to return. Once exhausted, each successive call to the __next__ method must raise the built-in StopIteration exception. Violating this requirement will cause the iterator to malfunction and prevent the for loop from terminating properly.
Consider the following example, which appears to work correctly:
1class MyIterator:
2 def __next__(self):
3 return 1
4
5class SomeIterableCollection:
6
7 def __iter__(self):
8 return MyIterator()
9
10for item in SomeIterableCollection():
11 print(item)
However, Python will not recognize this as a proper iterator:
1from collections.abc import Iterator
2
3class MyIterator:
4
5 def __next__(self):
6 return 1
7
8assert isinstance(MyIterator(), Iterator)
Why does this assertion fail? Python requires iterators to follow the complete iterator protocol (see Iterator protocol), which demands that iterators must themselves be iterable objects. This ensures iterators can be used seamlessly in for loops. The example above violates this requirement, as demonstrated when attempting to iterate directly over the iterator:
1class MyIterator:
2
3 def __next__(self):
4 return 1
5
6for item in MyIterator():
7 print(item)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[1], line 8
5 def __next__(self):
6 return 1
----> 8 for item in MyIterator():
9 print(item)
TypeError: 'MyIterator' object is not iterable
3.3. Iterator protocol#
To make an iterator a proper iterable object, two methods must be implemented:
__next__(self)— returns the next element of an iterator__iter__(self)— returns an iterator (conventionally, the object itself)
With both methods implemented correctly, the following code executes without error:
1class MyIterator:
2
3 def __iter__(self):
4 return self
5
6 def __next__(self):
7 # This is an infinite iterator that never raises StopIteration
8 return 1
9
10for item in MyIterator():
11 print(item)
We can verify this implementation satisfies the iterator protocol:
1from collections.abc import Iterator
2
3assert isinstance(MyIterator(), Iterator)
Iterator subclassing
As demonstrated above, subclassing the Iterator abstract class is not required to create an iterator[2] [Python Software Foundation, 2025]. However, using the collections.abc module is recommended when working with collections and iterators. When explicitly subclassing Iterator, the __iter__ method need not be implemented explicitly, as the superclass provides it as a mixin method.
1from collections.abc import Iterator
2
3class MyIterator(Iterator):
4
5 def __next__(self):
6 # Note: this is an infinite iterator
7 # as it never raises StopIteration exception
8 return 1
Iterator can be asynchronous
To write asynchronous iterator, you should use methods __anext__ and __aiter__
3.4. Iterable sources of data#
Having established how iterators work, let us examine iterable sources of data in greater detail. An object is iterable if it satisfies at least one of the following conditions [Ka-Ping and van Rossum, 2001]:
The object implements the
__getitem__dunder method for data access (i.e., it is a sequential or mapping collection [Python Software Foundation, 2025]):
my_list = [1, 2, 3]
idx = 1
item = my_list[idx] # equivalent to my_list.__getitem__(idx)
assert item == 2
The object implements the
__iter__dunder method, which returns an iterator object (an instance of a type compliant with the Iterator Protocol):
from collections.abc import Iterator
my_list = [1, 2, 3]
list_iter = iter(my_list) # or my_list.__iter__()
assert isinstance(list_iter, Iterator)