__await__ 是否需要成为生成器?

Does __await__ need to be a generator?

我想实现一个 awaitable 并注意到 __await__ 'needs' 是一个生成器。

来自 PEP-492:

An object with an __await__ method returning an iterator.

...

Objects with __await__ method are called Future-like objects in the rest of this PEP.

It is a TypeError if __await__ returns anything but an iterator.

根据我的经验,在 await 是一个语句之前,yield from 与作为生成器实现的协程一起使用。如今 python(我使用的是 3.5)具有使用 async def 语法的异步方法。因此,我将 yield from 语法视为 old/deprecated.

所以我打开了解释器来查看 how/if 这行得通:

>>> class A:
...     def __await__(self):
...         yield from (asyncio.sleep(1).__await__())
...         return 'spam'
... 
>>> a = A()
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.5/asyncio/base_events.py", line 467, in run_until_complete
    return future.result()
  File "/usr/lib64/python3.5/asyncio/futures.py", line 294, in result
    raise self._exception
  File "/usr/lib64/python3.5/asyncio/tasks.py", line 240, in _step
    result = coro.send(None)
  File "/usr/lib64/python3.5/asyncio/tasks.py", line 585, in _wrap_awaitable
    return (yield from awaitable.__await__())
  File "<stdin>", line 3, in __await__
AttributeError: 'generator' object has no attribute '__await__'

看来 asyncio.sleep 没有 __await__ 方法。使用这种 yield from 语法也感觉很别扭。

所以我决定尝试使用 async 语法,看看它是否有效:

>>> class A:
...     async def __await__(self):
...         await asyncio.sleep(1)
...         return 'spam'
... 
>>> a = A()
>>> 
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(a)
'spam'

它似乎确实有效!所以现在我想知道,__await__ 方法真的 需要 成为使用 yield from 语法的生成器吗?


编辑:添加间接级别时,在 await 语句中使用可等待对象,问题变得明显:

>>> async def test():
...     return await A()
... 
>>> loop.run_until_complete(test())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
    return future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "<stdin>", line 2, in test
TypeError: __await__() returned a coroutine

因此实际上需要像这样返回一个生成器:

class A:
    def __await__(self):
        yield from asyncio.sleep(1)
        return 'spam'    

So it appears asyncio.sleep doesn't have the __await__ method

是的,但不一定要等待。 documentation__await__,如果存在,需要 return 一个迭代器,而不是说 await 只适用于定义了 __await__ 的对象。事实上,它明确记录了 await 的参数可以是 之一:

  • 本机协程对象return从本机协程函数编辑。

  • 一个 generator-based 协程 对象 return 从装饰有 types.coroutine().

    的函数中编辑
  • 具有 __await__ 方法的对象 return 迭代器。

  • C 中定义的对象,提供 __await__ 特殊方法的 Python/C 等价物。

So now I'm wondering, does the __await__ method really need to be a generator using the yield from syntax?

如果您确实有一个 __await__ 方法,它确实需要 return 一个迭代器。

为了在 await 表达式中工作,__await__ 不需要是生成器。但是,某些操作仅在 __await__ 的结果支持生成器接口时才可用。

即,无法将 send 值或 throw 异常放入迭代器 -__await__。只有 None 可以 "sent" 到迭代器-__await__,就像使用 generator.__next__ 一样。


让我们考虑一个简单的 Awaitable,returns 来自其 __await__ 的迭代器。

class Iter:
    """Basic iterator that yields the same value"""
    def __next__(self): return 1
    def __iter__(self): return self

class IterAwait:
    """Awaitable that uses an iterator for __await__"""
    def __await__(self):
        return Iter()

我们可以检查它们是否实现了所需的接口:

>>> from collections.abc import Awaitable, Iterator, Generator
>>> isinstance(IterAwait(), Awaitable)
True
>>> isinstance(IterAwait().__await__(), Iterator)
True
>>> isinstance(IterAwait().__await__(), Generator)
False

为了查看它如何与 await 交互,我们将其包装在协程中:

async def iter_await():
    await IterAwait()

我们使用完整的 coroutine/generator 接口在 iter_await 上执行的每个操作都由 await 转发给我们的迭代器-__await__。这允许研究迭代器-__await__ 是如何接收信号的:

>>> 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 coroutine
1
>>> test_iter.send(None)      # 2. repeatedly use the underlying iterator
1
>>> next(test_iter)           # 3. do we expose the iterator?
TypeError: 'coroutine' object is not 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 one
1
>>> 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

可以看出,await可以处理迭代器-__await__,但不会转发所有操作。不过有的翻译了,有的早处理了。

  • 总是可以.send(None),翻译成裸__next__()。 (1, 2)
  • 协程不会神奇地公开 .__next__ (3) 也无法将 .send 转换为值 (4).
  • 有可能 .throw 异常,但 await 在协程的早期处理它。

请注意,await 使用可用的 throwsend 方法。如果 __await__ 的结果实现了 send 而不是 throw ,反之亦然,则使用存在的功能。只有 __next__ 是强制性的。