pyguides

__await__

What __await__ Does

__await__ is the dunder method that makes an object awaitable. When Python evaluates await obj, it calls obj.__await__() and then drives the returned iterator to completion, suspending and resuming your coroutine as values are yielded.

The method must return an iterator. In practice, this is almost always a generator, because generators have the suspension machinery that await depends on.

# Minimal __await__ — just yields to suspend
def __await__(self):
    yield  # suspends; resumed by the asyncio event loop

This is the core protocol behind asyncio.Task, asyncio.Future, and every async def coroutine. Python’s async model bottoms out in a yield somewhere in the chain — __await__ is the escape hatch that lets any object plug into that machinery.


Signature and Return Value

def __await__(self):
    # must return an iterator — typically a generator

__await__ takes no arguments beyond self. It returns an iterator, not a coroutine or an awaitable directly. The iterator is what Python’s await protocol drives to completion.

The idiomatic way to implement __await__ uses yield from:

def __await__(self):
    return (yield from self._coro())

yield from is the standard pattern because it:

  • Automatically unwraps nested iterators and propagates values.
  • Passes the final return value of the sub-iterator back as the result of await.
  • Connects naturally to Python’s generator-based suspension model.

The parentheses around (yield from ...) ensure the expression is evaluated as a single value before being returned. Python evaluates the right side first, iterates it, and the generator’s return value becomes what __await__ returns.


Minimal Example

import asyncio

class DelayedValue:
    """Wraps a value as an awaitable, resolving after a delay."""
    def __init__(self, value, delay=1.0):
        self.value = value
        self.delay = delay

    def __await__(self):
        # yield from delegates to asyncio.sleep, which is a coroutine
        return (yield from asyncio.sleep(self.delay, self.value))

async def main():
    result = await DelayedValue(42, 0.5)
    print(result)  # 42

asyncio.run(main())

Every await expression eventually chains down to a yield. The yield from asyncio.sleep(...) is that chain’s bottom — a real coroutine that the event loop drives forward.


__await__ vs __aiter__ / __anext__

These are two separate async protocols that people often mix up.

ProtocolMethodsUsed WithPurpose
Awaitable__await__await objPause until the object resolves to a single value
Async iteration__aiter__ + __anext__async for x in objIterate over multiple values asynchronously

With __await__, you call it once and await it once — like waiting for a single result. With async for, Python calls __aiter__ once to get the iterator, then repeatedly calls __anext__ until it raises StopAsyncIteration.

import asyncio

class AsyncCounter:
    """Async iterator — yields numbers with a delay between each."""
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    async def __aiter__(self):
        return self

    def __anext__(self):
        if self.current >= self.limit:
            raise StopAsyncIteration
        val = self.current
        self.current += 1
        return asyncio.sleep(0.1, val)

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

asyncio.run(main())

__anext__ must return an awaitable — an object with __await__. That’s where the two protocols touch: async iteration calls __anext__, gets an awaitable back, and awaits it. But the mechanisms are independent.


__await__ and __call__ — Parallel Protocols

Both __await__ and __call__ are dunder methods that integrate custom objects into native Python syntax:

MethodMakes object callable asSyntax
__call__(self, ...)A callableobj()
__await__(self)An awaitableawait obj

__call__ lets an object behave like a function. __await__ lets an object behave like a coroutine. Neither requires the object to subclass anything — just implement the method.

Regular async def functions and asyncio.Task objects implement __await__ implicitly. Custom objects that want to be awaitable must implement it explicitly and return a proper iterator.


TypeError When __await__ Is Missing

Attempting to await an object that doesn’t provide __await__ raises:

TypeError: object X can't be used in 'await' expression

Python checks for __await__ before doing anything else. Common cases:

await None
# TypeError: object NoneType can't be used in 'await' expression

await {"a": 1}
# TypeError: object dict can't be used in 'await' expression

The fix is to either use an actual coroutine, wrap with asyncio.ensure_future(), or implement __await__ on your class.


Standard Library Internals

asyncio.Future.__await__

def __await__(self):
    if not self.done():
        self._asyncio_future_blocking = True
        yield self  # suspends; Task waits for the future to resolve
    if not self.done():
        raise RuntimeError("await wasn't used with future")
    return self.result()

The yield self is the suspension point. It tells the Task driving this coroutine to wait. When the future is resolved from outside (e.g., a callback sets its result), the Task resumes the generator and the result propagates up.

Coroutine.__await__ (abstract)

@abstractmethod
def __await__(self):
    yield  # coroutines are awaitable because generators yield internally

Every async def function creates a coroutine object that implements __await__. The implicit yield inside every coroutine is what makes it awaitable — you never see it in your code, but it’s there in the generated bytecode.


Custom Awaitable Example

class MyFuture:
    """Minimal awaitable that resolves when resolve() is called."""
    def __init__(self):
        self._result = None
        self._done = False

    def resolve(self, value):
        self._result = value
        self._done = True

    def __await__(self):
        if not self._done:
            yield self  # suspends until resolve() is called
        return self._result

import asyncio

async def main():
    future = MyFuture()
    asyncio.get_event_loop().call_soon(future.resolve, "done!")
    result = await future
    print(result)  # done!

asyncio.run(main())

This mirrors what asyncio.Future does internally. The key pattern is yield self as the suspension signal, with resolution happening externally — typically from an event loop callback or another coroutine.


See Also

  • async-await-patterns — patterns for building async pipelines with asyncio.gather(), create_task(), and awaitable chains
  • asyncio-basics — coroutines, the event loop, and how await drives suspension
  • __call__ — the dunder method that makes objects callable like functions