Does __await__ Need To Be A Generator?
Solution 1:
In order to work in an await expression, __await__ does not need to be a generator. However, certain operations are only available if the result of __await__ support the generator interface.
Namely, it is not possible to send values or throw exceptions into an iterator-__await__. Only None can be "sent" to an iterator-__await__, as if generator.__next__ were used.
Let's consider a simple Awaitable that returns an iterator from its __await__.
classIter:
    """Basic iterator that yields the same value"""def__next__(self): return1def__iter__(self): return self
classIterAwait:
    """Awaitable that uses an iterator for __await__"""def__await__(self):
        return Iter()
We can check that they implement the desired interfaces:
>>>from collections.abc import Awaitable, Iterator, Generator>>>isinstance(IterAwait(), Awaitable)
True
>>>isinstance(IterAwait().__await__(), Iterator)
True
>>>isinstance(IterAwait().__await__(), Generator)
False
In order to see how this interacts with await, we wrap it in a coroutine:
async def iter_await():
    awaitIterAwait()
Every operation we perform on iter_await with the full coroutine/generator interface is forwarded by await to our iterator-__await__. This allows to study how the iterator-__await__ receives signals:
>>> test_iter = iter_await()
>>> test_iter.send(3)         # 0. does it appear like a coroutine?
TypeError: can`t send non-None value to a just-started coroutine
>>> test_iter.send(None)      # 1. must initialise just-started coroutine1>>> test_iter.send(None)      # 2. repeatedly use the underlying iterator1>>> next(test_iter)           # 3. do we expose the iterator?
TypeError: 'coroutine'objectisnot an iterator
>>> test_iter.send(3)         # 4. can we send non-None values?
AttributeError: 'Iter'object has no attribute 'send'>>> test_iter = iter_await()  # we just broke the coroutine...>>> test_iter.send(None)      # ...need a new one1>>> test_iter.throw(KeyError) # 4. can we throw Exceptions?
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in iter_await
KeyError
As can be seen, await can handle an iterator-__await__, but does not forward all operations. However, some are translated and some are handled early.
- It is always possible to 
.send(None), which is translated to a bare__next__(). (1, 2) - The coroutine does not magically expose 
.__next__(3) and cannot translate.sendwith a value either (4). - It is possible to 
.throwan exception, butawaithandles it early in the coroutine. 
Note that await uses the throw and send method as available. If the result of __await__ implements send but not throw or vice versa, the functionality present is used. Only __next__ is mandatory.
Solution 2:
So it appears
asyncio.sleepdoesn't have the__await__method
True, but it doesn't have to have one to be awaitable. The documentation says that __await__, if present, needs to return an iterator, not that await will only work on objects that define __await__. In fact, it explicitly documents that the argument to await can be one of:
A native coroutine object returned from a native coroutine function.
A generator-based coroutine object returned from a function decorated with
types.coroutine().An object with an
__await__method returning an iterator.An object defined in C providing the Python/C equivalent of the
__await__special method.
So now I'm wondering, does the
__await__method really need to be a generator using the yield from syntax?
If you actually have an __await__ method, it does need to return an iterator.
Post a Comment for "Does __await__ Need To Be A Generator?"