__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.
| Protocol | Methods | Used With | Purpose |
|---|---|---|---|
| Awaitable | __await__ | await obj | Pause until the object resolves to a single value |
| Async iteration | __aiter__ + __anext__ | async for x in obj | Iterate 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:
| Method | Makes object callable as | Syntax |
|---|---|---|
__call__(self, ...) | A callable | obj() |
__await__(self) | An awaitable | await 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
awaitdrives suspension - __call__ — the dunder method that makes objects callable like functions