pyguides

__aiter__ / __anext__

__aiter__ and __anext__ are the two dunder methods that power Python’s asynchronous iteration protocol. They do for async for what __iter__ and __next__ do for a regular for loop — but with a critical difference: every step through the loop is awaited.

These methods landed in Python 3.5 via PEP 492, which introduced coroutines and the async/await syntax as first-class language features.

How async for calls them

When Python encounters async for item in obj, it follows a predictable sequence:

# What you write:
async for item in obj:
    # loop body

# What Python does internally:
_iter = obj.__aiter__()
while True:
    try:
        item = await _iter.__anext__()
    except StopAsyncIteration:
        break
    # loop body

__aiter__() runs once at the start to get the async iterator object. Then __anext__() runs on every iteration, returning an awaitable that Python awaits before feeding the value into the loop body. When the iterator is exhausted, __anext__() raises StopAsyncIteration and the loop exits.

__aiter__(self)

__aiter__ is called once when the async for loop begins. Its job is to return the async iterator object.

For a class that is itself the iterator, __aiter__ typically returns self:

def __aiter__(self):
    return self

For an async iterable factory (a class that produces fresh iterator objects on each call), __aiter__ returns a new instance of a separate iterator class:

def __aiter__(self):
    return AsyncIterator(self.start, self.stop)

One important detail: __aiter__ does not return an awaitable. It returns the async iterator directly. This was a deliberate change in Python 3.5.2 — the original design (Python 3.5.0–3.5.1) returned an awaitable from __aiter__, but that added unnecessary indirection and was removed. Modern Python (3.7+) requires __aiter__ to return the iterator object itself.

__anext__(self)

__anext__ is called repeatedly by async for to fetch each successive value. Unlike __next__() in the synchronous protocol, __anext__() does not return a value directly — it returns an awaitable (typically a coroutine from async def, or any object with __await__).

def __anext__(self):
    return self._fetch_next()  # returns an awaitable, not a value

When the iterator has no more values, __anext__ must raise StopAsyncIteration. This is mandatory — returning StopAsyncIteration instead of raising it does not work:

class BrokenIterator:
    def __aiter__(self):
        return self
    def __anext__(self):
        return StopAsyncIteration  # WRONG — must raise!

async def main():
    async for x in BrokenIterator():
        print(x)  # never runs, and no error is raised either

The loop never enters the body and never exits cleanly. Always use raise StopAsyncIteration inside __anext__.

StopAsyncIteration

This built-in exception is the async equivalent of StopIteration. It signals that the iterator has no more values. Unlike StopIteration (which inherits from BaseException), StopAsyncIteration inherits from Exception, which means it won’t be silently swallowed by overly broad exception handlers in code that calls your iterator.

import asyncio

class AsyncCounter:
    def __init__(self, stop: int):
        self.current = 0
        self.stop = stop

    def __aiter__(self):
        return self

    def __anext__(self):
        if self.current >= self.stop:
            raise StopAsyncIteration
        value = self.current
        self.current += 1
        return self._delay(value)

    async def _delay(self, value):
        await asyncio.sleep(0.01)
        return value

async def main():
    async for num in AsyncCounter(3):
        print(num)
    # Output: 0, 1, 2

asyncio.run(main())

Sync vs Async Iteration Protocol

The two protocols are structurally parallel, but the async version introduces awaits at every step:

AspectSyncAsync
Loop syntaxfor x in objasync for x in obj
Setup method__iter__()__aiter__()
ReturnsIterator objectAsync iterator object
Fetch method__next__()__anext__()
ReturnsPlain valueAwaitable (coroutine)
End signalraise StopIterationraise StopAsyncIteration
Loop awaits the fetch?NoYes

The fundamental distinction is that __next__() returns a value directly, while __anext__() returns an awaitable that the event loop suspends on until the value is ready. This is what allows async for to pause the current coroutine and let other tasks run between items.

Async Generators: Automatic Implementation

Writing __aiter__ and __anext__ by hand is instructive, but in practice the most common way to build an async iterator is with an async generator — an async def function that contains yield:

import asyncio

async def async_gen(stop: int):
    """Async generator — automatically implements __aiter__ and __anext__."""
    for i in range(stop):
        await asyncio.sleep(0.01)
        yield i

async def main():
    async for num in async_gen(3):
        print(num)
    # Output: 0, 1, 2

asyncio.run(main())

Python automatically gives an async generator a __aiter__ that returns self and a __anext__ that advances the generator and returns an awaitable. You don’t have to implement either method manually. This is the idiomatic approach for most async iteration use cases: API response streams, I/O pipelines, rate-limited fetchers, and similar.

__aiter__ / __anext__ vs __await__

These two async protocols serve different purposes:

  • __await__ → enables await obj → produces a single value
  • __aiter__ / __anext__ → enables async for obj → produces a sequence of values

An object can implement both. An async generator function (async def with yield) automatically implements all three: __await__, __aiter__, and __anext__.

class AwaitableValue:
    def __init__(self, value):
        self.value = value
    def __await__(self):
        return iter([self.value])

class AsyncIterator:
    def __aiter__(self):
        return self
    def __anext__(self):
        return self._fetch()
    async def _fetch(self):
        return self.value

Common Mistakes

Returning StopAsyncIteration instead of raising it. The async iteration protocol requires raise StopAsyncIteration. Returning it does nothing.

Returning a plain value from __anext__. The loop calls await on whatever __anext__ returns. If you return a non-awaitable, you’ll get TypeError: object int can't be used in 'await' expression.

Reusing a stateful iterator after StopAsyncIteration. Once __anext__ raises StopAsyncIteration, the iterator is done. Reusing it without resetting state produces no values:

async for num in AsyncCounter(3):
    print(num)
# Second iteration produces nothing — iterator already exhausted
async for num in AsyncCounter(3):  # must create a new instance
    print(num)

See Also

  • async-await-patternsasync for in context, plus asyncio.gather and other async patterns
  • asyncio-basics — Event loops, coroutines, and the asyncio module foundations
  • await — The awaitable protocol; __await__ enables await on an object the same way __aiter__/__anext__ enable async for