Last Updated on November 14, 2023
Iterators provide a way to traverse structures like lists of items in a linear way.
The problem is, that conventional iterators are not well suited to asyncio programs. The reason is that we cannot have each item in the iterator retrieved asynchronously.
Instead, we can use asynchronous iterators along with the async for expression to automatically await the retrieval of the next item in the iteration.
In this tutorial, you will discover how to develop and use asynchronous iterators.
After completing this tutorial, you will know:
- The difference between regular iterators and asynchronous iterators.
- How to develop asynchronous iterators for use in asyncio.
- How to use asynchronous iterators with the async for expression in asyncio.
Let’s get started.
What Are Asynchronous Iterators
An asynchronous iterator is an object that implements the __aiter__() and __anext__() methods.
Before we take a close look at asynchronous iterators, let’s review classical iterators.
Iterators
An iterator is a Python object that implements a specific interface.
Specifically, the __iter__() method that returns an instance of the iterator, and the __next__() method that steps the iterator one cycle and returns a value.
iterator: An object representing a stream of data. Repeated calls to the iterator’s __next__() method (or passing it to the built-in function next()) return successive items in the stream. When no more data are available a StopIteration exception is raised instead.
— Python Glossary
An iterator can be stepped using the next() built-in function or traversed using a for loop.
Many Python objects are iterable, most notable are containers such as lists.
Next, let’s consider asynchronous iterators.
Asynchronous Iterators
An asynchronous iterator is a Python object that implements a specific interface.
asynchronous iterator: An object that implements the __aiter__() and __anext__() methods.
— Python Glossary
An asynchronous iterator must implement the __aiter__() and __anext__() methods.
- The __aiter__() method must return an instance of the iterator.
- The __anext__() method must return an awaitable that steps the iterator.
An asynchronous iterator may only be stepped or traversed in an asyncio program, such as within a coroutine.
Asynchronous iterators were introduced in PEP 492 – Coroutines with async and await syntax.
An asynchronous iterator can be stepped using the anext() built-in function that returns an awaitable that executes one step of the iterator, e.g. one call to the __anext__() method.
An asynchronous iterator can be traversed using the “async for” expression that will automatically call anext() each iteration and await the returned awaitable in order to retrieve the return value.
An asynchronous iterable is able to call asynchronous code in its iter implementation, and asynchronous iterator can call asynchronous code in its next method.
— PEP 492 – Coroutines with async and await syntax
Now that we know what an asynchronous iterator is, let’s compare it to a classical iterator.
Iterators vs Asynchronous Iterators
Both classical iterators and asynchronous iterators have a lot in common.
They both traverse a stream of data one step at a time.
A classical iterator can be used generally anywhere in a Python program, whereas an asynchronous iterator can only be used in an asyncio Python program, such as called and used within a coroutine.
- Classical Iterator: Used generally anywhere in Python Program
- Asynchronous Iterator: Only used in asyncio programs.
The classical iterator can be stepped using the built-in next() function, whereas asynchronous iterators are traversed using the anext() built-in function.
- Classical Iterator: Stepped using the next() function.
- Asynchronous Iterator: Stepped using the anext() function.
The classical iterator is traversed with a classical for loop, whereas the asynchronous iterator must be traversed using an “async for” loop expression.
- Classical Iterator: Traversed using a for loop.
- Asynchronous Iterator: Traversed using an async for loop.
Each iteration of the asynchronous iterator returns an awaitable that must be awaited in an asyncio program. Therefore mixing the syntax of classical and asynchronous iterators will result in errors.
- Classical Iterator: Returns a value.
- Asynchronous Iterator: Returns an awaitable.
Next, let’s consider the relationship between asynchronous iterators and asynchronous generators.
Asynchronous Iterators and Asynchronous Generators
Asynchronous iterators and asynchronous generators are tightly related.
An asynchronous generator is a coroutine that makes use of the yield expression.
An asynchronous generator can be created which returns an “asynchronous generator iterator” which is a type of asynchronous iterator.
asynchronous generator iterator: An object created by a asynchronous generator function. This is an asynchronous iterator which when called using the __anext__() method returns an awaitable object which will execute the body of the asynchronous generator function until the next yield expression.
— Python Glossary
The asynchronous generator iterator can be used directly as an asynchronous iterator.
You can learn more about how to use asynchronous generators in the tutorial:
Now that we know about asynchronous iterators, let’s look at how we can define and use them.
Run loops using all CPUs, download your FREE book to learn how.
How to Use Asynchronous Iterators
In this section, we will take a close look at how to define, create, step, and traverse an asynchronous iterator in asyncio programs.
Let’s start with how to define an asynchronous iterator.
Define an Asynchronous Iterator
We can define an asynchronous iterator by defining a class that implements the __aiter__() and __anext__() methods.
These methods are defined on a Python object as per normal.
Importantly, because the __anext__() function must return an awaitable, it must be defined using the “async def” expression.
object.__anext__(self): Must return an awaitable resulting in a next value of the iterator. Should raise a StopAsyncIteration error when the iteration is over.
— Asynchronous Iterators
When the iteration is complete, the __anext__() method must raise a StopAsyncIteration exception.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# define an asynchronous iterator class AsyncIterator(): # constructor, define some state def __init__(self): self.counter = 0 # create an instance of the iterator def __aiter__(self): return self # return the next awaitable async def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # return the counter value return self.counter |
Because the asynchronous iterator is a coroutine and each iterator returns an awaitable that is scheduled and executed in the asyncio event loop, we can execute and await awaitables within the body of the iterator.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
... # return the next awaitable async def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # simulate work await asyncio.sleep(1) # return the counter value return self.counter |
Next, let’s look at how we might use an asynchronous iterator.
Create Asynchronous Iterator
To use an asynchronous iterator we must create the iterator.
This involves creating the Python object as per normal.
For example:
1 2 3 |
... # create the iterator it = AsyncIterator() |
This returns an “asynchronous iterable“, which is an instance of an “asynchronous iterator“.
Step an Asynchronous Iterator
One step of the iterator can be traversed using the anext() built-in function, just like a classical iterator using the next() function.
The result is an awaitable that is awaited.
For example:
1 2 3 4 5 |
... # get an awaitable for one step of the iterator awaitable = anext(it) # execute the one step of the iterator and get the result result = await awaitable |
This can be achieved in one step.
For example:
1 2 3 |
... # step the async iterator result = await anext(it) |
Traverse an Asynchronous Iterator
The asynchronous iterator can also be traversed in a loop using the “async for” expression that will await each iteration of the loop automatically.
For example:
1 2 3 4 |
... # traverse an asynchronous iterator async for result in AsyncIterator(): print(result) |
You can learn more about the “async for” expression in the tutorial:
We may also use an asynchronous list comprehension with the “async for” expression to collect the results of the iterator.
For example:
1 2 3 |
... # async list comprehension with async iterator results = [item async for item in AsyncIterator()] |
You can learn more about asynchronous list comprehensions in the tutorial:
Now that we know how to create and use asynchronous iterators, let’s look at worked examples.
Example of One Step of an Asynchronous Iterator with anext()
We can explore how to create an asynchronous iterator and step it one time.
This is a good approach to understanding more deeply what exactly is happening with the iterator when it is traversed using an “async for” expression.
In this example, we will first define an asynchronous iterator that loops a number of times, suspends each iteration, and returns an integer value. The constructor of the iterator defines a counter and initializes it to zero, this counter is updated each time the iterator is progressed with a call to __anext__().
Awaiting within the asynchronous iterator highlights the coroutine nature of the iterator in that it is able to suspend and await other coroutines and tasks.
The main coroutine will create the iterator, get an awaitable that progresses the iterator one step, then await the awaitable in order to retrieve the yielded value.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# SuperFastPython.com # example of one step of an asynchronous iterator import asyncio # define an asynchronous iterator class AsyncIterator(): # constructor, define some state def __init__(self): self.counter = 0 # create an instance of the iterator def __aiter__(self): return self # return the next awaitable async def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # simulate work await asyncio.sleep(1) # return the counter value return self.counter # main coroutine async def main(): # create the async iterator it = AsyncIterator() # step the iterator one iteration awaitable = anext(it) # get the result from one iteration result = await awaitable # report the result print(result) # execute the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point into the asyncio program.
The main() coroutine runs and creates an instance of the asynchronous iterator. This initializes the internal counter to zero.
It then calls the anext() function on the iterator. This returns an awaitable, that if awaited will execute the iterator’s __anext__() method.
The awaitable is awaited. This suspends the main() coroutine and executes the asynchronous iterator that itself suspends a moment and then returns an integer value.
The main() coroutine resumes and retrieves the returned value and reports it.
The program then terminates.
This highlights what is happening when an asynchronous iterator is executed, such as each iteration within an “async for” expression.
1 |
1 |
Next, let’s look at how we might traverse an asynchronous iterator using the “async for” expression.
Free Python Asyncio Course
Download your FREE Asyncio PDF cheat sheet and get BONUS access to my free 7-day crash course on the Asyncio API.
Discover how to use the Python asyncio module including how to define, create, and run new coroutines and how to use non-blocking I/O.
Example of an Asynchronous Iterator with async for
We can explore how to traverse an asynchronous iterator using the “async for” expression.
In this example, we will update the previous example to traverse the iterator to completion using an “async for” loop.
This loop will automatically await each awaitable returned from the iterator, retrieve the returned value, and make it available within the loop body so that in this case it can be reported.
This is perhaps the most common usage pattern for asynchronous iterators.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# SuperFastPython.com # example of an asynchronous iterator with async for loop import asyncio # define an asynchronous iterator class AsyncIterator(): # constructor, define some state def __init__(self): self.counter = 0 # create an instance of the iterator def __aiter__(self): return self # return the next awaitable async def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # simulate work await asyncio.sleep(1) # return the counter value return self.counter # main coroutine async def main(): # loop over async iterator with async for loop async for item in AsyncIterator(): print(item) # execute the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point into the asyncio program.
The main() coroutine runs and starts the for loop.
An instance of the asynchronous iterator is created and the loop automatically steps it using the anext() function to return an awaitable. The loop then awaits the awaitable and retrieves a value which is made available to the body of the loop where it is reported.
This process is then repeated, suspending the main() coroutine, executing a step of the iterator and suspending, and resuming the main() coroutine until the iterator is exhausted.
Once the internal counter of the iterator reaches 10, a StopAsyncIteration is raised. This does not terminate the program. Instead, it is expected and handled by the “async for” expression and breaks the loop.
This highlights how an asynchronous iterator can be traversed using an async for expression.
1 2 3 4 5 6 7 8 9 10 |
1 2 3 4 5 6 7 8 9 10 |
Next, let’s look at how we might use an asynchronous iterator with an asynchronous list comprehension.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of an Asynchronous Iterator with Asynchronous Comprehension
We can explore how to use an asynchronous iterator with an asynchronous list comprehension.
In this example, we will update the above example to traverse the asynchronous iterator to completion.
In this case, we will traverse it within an asynchronous list comprehension, rather than an “async for” loop. This will collect the returned values into a list that can be reported.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# SuperFastPython.com # example of an asynchronous iterator with async comprehension import asyncio # define an asynchronous iterator class AsyncIterator(): # constructor, define some state def __init__(self): self.counter = 0 # create an instance of the iterator def __aiter__(self): return self # return the next awaitable async def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # simulate work await asyncio.sleep(1) # return the counter value return self.counter # main coroutine async def main(): # loop over async iterator with async comprehension results = [item async for item in AsyncIterator()] # report results print(results) # execute the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point into the asyncio program.
The main() coroutine runs and executes the asynchronous list comprehension.
This comprehension operates just like the async for loop in the previous section. Each iteration retrieves an awaitable, the awaitable is awaited and suspends the main() coroutine, and an iteration of the iterator executes and returns a value which is then collected in the resumed main() coroutine.
The process is repeated until all values are collected into a list.
Once the asynchronous list comprehension is completed, the values are then reported.
This highlights how we might traverse an asynchronous iterator using an asynchronous list comprehension.
1 |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
Next, let’s look at some common errors when using asynchronous iterators.
Common Errors with Asynchronous Iterators
It is common to stumble when getting started with asynchronous iterators.
In this section, we will explore a number of common errors when using asynchronous iterators and how they may be overcome.
Do you have an error not listed below?
Let me know in the comments below and I will do my best to help.
Error Using Asynchronous Iterator with next()
We can explore what happens when we try to use an asynchronous iterator with the next() built-in function, instead of the anext() function.
In this example, we will create the iterator, then attempt to step it one iteration.
We will use the next() function on the asynchronous iterator, which will result in an error.
We will get an error because the next() function expects to operate on an iterator, and instead it has received an asynchronous iterator that implements a different interface.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# SuperFastPython.com # example of error using an asynchronous iterator with next() import asyncio # define an asynchronous iterator class AsyncIterator(): # constructor, define some state def __init__(self): self.counter = 0 # create an instance of the iterator def __aiter__(self): return self # return the next awaitable async def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # simulate work await asyncio.sleep(1) # return the counter value return self.counter # main coroutine async def main(): # create the async iterator gen = AsyncIterator() # step the iterator one iteration (results in an error) awaitable = next(gen) # execute the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point into the asyncio program.
The main() coroutine runs and creates the asynchronous iterator as per normal.
The next() function is then called on the asynchronous iterator which results in a TypeError exception and terminates the program.
The exception occurs because the next() function is expecting an iterator and was instead provided an asynchronous iterator that implements a different interface.
The fix involves using the anext() function on the asynchronous iterator instead of the next() function.
1 2 3 |
Traceback (most recent call last): ... TypeError: 'AsyncIterator' object is not an iterator |
Next, let’s look at using an asynchronous iterator with a “for” expression.
Error Using Asynchronous Iterator with for loop
We can explore what happens if we try to use an asynchronous iterator with a “for” expression, instead of an “async for” expression.
In this example, we will create the iterator and then attempt to traverse it to completion using a classical for-loop.
This will fail because the for-loop expects an iterator and instead receives an asynchronous iterator that implements a different interface.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# SuperFastPython.com # example of error using an asynchronous iterator with a for loop import asyncio # define an asynchronous iterator class AsyncIterator(): # constructor, define some state def __init__(self): self.counter = 0 # create an instance of the iterator def __aiter__(self): return self # return the next awaitable async def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # simulate work await asyncio.sleep(1) # return the counter value return self.counter # main coroutine async def main(): # classical for loop over async iterator (relists in error) for item in AsyncIterator(): print(item) # execute the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point into the asyncio program.
The main() coroutine runs and creates an instance of the asynchronous iterator in the loop and begins iterating.
This fails with a TypeError exception because we provided an asynchronous iterator, not a classical iterator as expected by the for-loop expression.
The fix involves using an “async for” expression instead of a “for” expression.
1 2 3 |
Traceback (most recent call last): ... TypeError: 'AsyncIterator' object is not iterable |
Next, let’s look at using an asynchronous iterator that does not return an awaitable.
Error Using Asynchronous Iterator without Awaitable
We can explore what happens if we try to use an asynchronous iterator that does not return an awaitable when progressed.
In this example, we will update the definition of the asynchronous iterator so that the __anext__() method is defined as a normal object method, not as a coroutine with the “async def” expression.
This will fail with a TypeError when the iterator is progressed because the “async for” expression will expect an awaitable to be returned, not the return value of the iterable.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# SuperFastPython.com # example of an asynchronous iterator without an awaitable import asyncio # define an asynchronous iterator class AsyncIterator(): # constructor, define some state def __init__(self): self.counter = 0 # create an instance of the iterator def __aiter__(self): return self # return the next awaitable def __anext__(self): # check for no further items if self.counter >= 10: raise StopAsyncIteration # increment the counter self.counter += 1 # return the counter value return self.counter # main coroutine async def main(): # loop over async iterator with async for loop async for item in AsyncIterator(): print(item) # execute the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and uses it as the entry point into the asyncio program.
The main() coroutine runs and creates an instance of the asynchronous iterator in the loop and begins iterating.
The iteration fails with a TypeError as expected. This is because the “async for” expression expects an awaitable to be returned from the call to __anext__(), except in this case a return value was returned.
The fix involves defining the __anext__() method as a coroutine using the “async def” expression.
1 2 3 4 5 6 7 |
TypeError: object int can't be used in 'await' expression The above exception was the direct cause of the following exception: Traceback (most recent call last): ... TypeError: 'async for' received an invalid object from __anext__: int |
Further Reading
This section provides additional resources that you may find helpful.
Python Asyncio Books
- Python Asyncio Mastery, Jason Brownlee (my book!)
- Python Asyncio Jump-Start, Jason Brownlee.
- Python Asyncio Interview Questions, Jason Brownlee.
- Asyncio Module API Cheat Sheet
I also recommend the following books:
- Python Concurrency with asyncio, Matthew Fowler, 2022.
- Using Asyncio in Python, Caleb Hattingh, 2020.
- asyncio Recipes, Mohamed Mustapha Tahrioui, 2019.
Guides
APIs
- asyncio — Asynchronous I/O
- Asyncio Coroutines and Tasks
- Asyncio Streams
- Asyncio Subprocesses
- Asyncio Queues
- Asyncio Synchronization Primitives
References
Takeaways
You now know how to create and use asynchronous iterators in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Remy Lovesy on Unsplash
Do you have any questions?