__await__需要是一个生成器吗?

时间:2018-03-09 09:28:32

标签: python python-3.5 python-asyncio

我想实现一个等待的,并注意到__await__'需要'成为一个生成器。

来自PEP-492

  

使用__await__方法返回迭代器的对象。

     

...

     

具有__await__方法的对象在本PEP的其余部分中称为类似Future的对象。

     

如果__await__返回除迭代器之外的任何内容,则为TypeError。

根据我的经验,在await作为声明之前,yield from与作为生成器实现的协同程序一起使用。现在python(我使用3.5)使用async def语法的异步方法。因此,我认为yield from语法为旧/已弃用。

所以我打破了解释器,看看它是如何工作的:

>>> 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'    

2 个答案:

答案 0 :(得分:2)

  

所以看来asyncio.sleep没有__await__方法

是的,但它并不一定要等待。 documentation表示__await__(如果存在)需要返回迭代器,而不是await仅适用于定义__await__的对象。实际上,它明确地证明await的参数可以是 中的一个:

  • 从原生协程函数返回的 native coroutine 对象。

  • 从装饰有types.coroutine()的函数返回的基于生成器的协同程序对象。

  • 使用__await__方法返回迭代器的对象。

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

  

所以现在我想知道,__await__方法真的需要使用语法中的yield吗?

如果你实际上有一个__await__方法,它确实需要返回一个迭代器。

答案 1 :(得分:0)

要使用await表达式,__await__不必是生成器。但是,只有__await__的结果支持生成器接口时,某些操作才可用。

即,不可能将send个值或throw例外放入迭代器__await__中。只能None“发送”到迭代器__await__,就像使用了generator.__next__一样。


让我们考虑一个简单的Awaitable,它从其__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()

我们在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__是必填项。