ZetCode

Python aiter Function

Last modified April 11, 2025

This comprehensive guide explores Python's aiter function, which returns an asynchronous iterator for asynchronous iteration. We'll cover basic usage, custom async iterators, and practical examples.

Basic Definitions

The aiter function returns an asynchronous iterator object. It is the async equivalent of the built-in iter function. Introduced in Python 3.10, it works with objects implementing __aiter__.

Key characteristics: used in async for loops, requires an async iterable, returns an async iterator. It is typically used with anext for asynchronous iteration.

Basic Async Iteration

Here's simple usage with an async generator showing how aiter works with basic async iteration.

basic_aiter.py
async def async_gen():
    for i in range(3):
        yield i
        await asyncio.sleep(0.1)

async def main():
    ag = async_gen()
    ait = aiter(ag)
    
    while True:
        try:
            val = await anext(ait)
            print(val)
        except StopAsyncIteration:
            break

asyncio.run(main())

This example shows basic async iteration. The async_gen produces values asynchronously. aiter gets the async iterator, and anext retrieves values.

The loop continues until StopAsyncIteration is raised, similar to regular iteration but with async/await syntax.

Custom Async Iterator

You can create custom async iterators by implementing __aiter__. This example shows a simple async counter.

custom_aiter.py
class AsyncCounter:
    def __init__(self, stop):
        self.stop = stop
        self.current = 0
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        if self.current >= self.stop:
            raise StopAsyncIteration
        await asyncio.sleep(0.1)
        self.current += 1
        return self.current - 1

async def main():
    async for i in AsyncCounter(3):
        print(i)

asyncio.run(main())

The AsyncCounter class implements __aiter__ and __anext__ to support async iteration. aiter would return this object when called.

This pattern is useful when you need more control over async iteration than async generators provide.

Async Iterable Protocol

This example demonstrates the full async iterable protocol with separate iterator and iterable classes.

protocol.py
class AsyncIterable:
    def __init__(self, data):
        self.data = data
    
    def __aiter__(self):
        return AsyncIterator(self.data)

class AsyncIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0
    
    async def __anext__(self):
        if self.index >= len(self.data):
            raise StopAsyncIteration
        await asyncio.sleep(0.1)
        item = self.data[self.index]
        self.index += 1
        return item

async def main():
    ait = aiter(AsyncIterable([1, 2, 3]))
    print(await anext(ait))  # 1
    print(await anext(ait))  # 2
    print(await anext(ait))  # 3

asyncio.run(main())

This shows the complete protocol with separate iterable and iterator classes. The AsyncIterable returns a new AsyncIterator.

This separation allows multiple independent iterations over the same data, similar to how regular iterables work.

Error Handling

The aiter function raises TypeError when used with non-async iterables. This example shows proper error handling.

errors.py
async def main():
    try:
        ait = aiter([1, 2, 3])  # Regular list is not async iterable
    except TypeError as e:
        print(f"Error: {e}")  # 'list' object is not async iterable

    class NoAiter:
        pass
    
    try:
        ait = aiter(NoAiter())
    except TypeError as e:
        print(f"Error: {e}")  # 'NoAiter' object is not async iterable

asyncio.run(main())

These examples demonstrate aiter's behavior with invalid types. Regular iterables and objects without __aiter__ raise errors.

To make a class work with aiter, implement __aiter__ as shown in previous examples.

Practical Example: Async Data Fetch

This example shows a practical use case for aiter with async data fetching from multiple sources.

data_fetch.py
async def fetch_data(url):
    # Simulate network request
    await asyncio.sleep(0.2)
    return f"Data from {url}"

class AsyncDataFetcher:
    def __init__(self, urls):
        self.urls = urls
    
    def __aiter__(self):
        self.index = 0
        return self
    
    async def __anext__(self):
        if self.index >= len(self.urls):
            raise StopAsyncIteration
        url = self.urls[self.index]
        self.index += 1
        return await fetch_data(url)

async def main():
    fetcher = AsyncDataFetcher([
        "api.example.com/1",
        "api.example.com/2",
        "api.example.com/3"
    ])
    
    async for data in fetcher:
        print(data)

asyncio.run(main())

This demonstrates a real-world use case where aiter enables asynchronous data processing. The fetcher retrieves data from URLs one by one.

The async iteration pattern allows efficient I/O-bound operations without blocking the event loop.

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.