【问题标题】:Precise specification of __await____await__ 的精确说明
【发布时间】:2021-01-05 21:07:44
【问题描述】:

Python 语言参考指定object.__await__ 如下:

object.__await__(self)

必须返回一个迭代器。应该用于实现等待对象。例如,asyncio.Future 实现此方法以兼容 await 表达式。

就是这样。我发现这个规范非常模糊而且不是很具体(具有讽刺意味的是)。好的,它应该返回一个迭代器,但它可以是一个任意的迭代器吗?显然不是:

import asyncio


class Spam:
    def __await__(self):
        yield from range(10)


async def main():
    await Spam()


asyncio.run(main())
RuntimeError: Task got bad yield: 0

我假设asyncio 事件循环期望迭代器产生一种特定类型的对象。那么它应该究竟产生什么? (为什么没有记录?)


编辑:据我所知,这在任何地方都没有记录。但我一直在自己调查,我认为了解asyncio 期望其协程产生哪些对象的关键在于_asynciomodule.c 中的task_step_impl


更新:我已经对 cpython 存储库进行了 PR,目的是澄清这一点:"Clarify the vague specification of object.__await__"。目前正在审核中。

【问题讨论】:

  • 删除了我的答案。我以为那里有没有的信息。
  • 请注意,您无需阅读 C 代码即可理解 asyncio 事件循环。 C 代码只是规范 Python code 的加速版本,为了诸如 PyPy 等非 C 支持的实现,它被保留并积极维护。

标签: python async-await python-asyncio


【解决方案1】:

任务只能等待其他任务/未来。来自CPython source code

    /* Check if `result` is FutureObj or TaskObj (and not a subclass) */
    /* ... */

    /* Check if `result` is None */
    /* ... error */

    /* Check if `result` is a Future-compatible object */
    /* ... */

    /* Check if `result` is a generator */
    /* ... */

    /* The `result` is none of the above */
    o = task_set_error_soon(
        task, PyExc_RuntimeError, "Task got bad yield: %R", result);
    Py_DECREF(result);
    return o;

编辑:如果我理解正确,此限制仅适用于任务,并且正常的期货可以等待从__await__ 返回的任何可迭代对象,尽管重点可能是可迭代对象返回的结果是事件循环,然后最终结束返回结果。

【讨论】:

  • 是的,我通过深入研究 _asynciomodule.c 中的 CPython 代码(特别是 task_step_impl,这是您引用的函数)基本上发现了相同的内容。现在的问题是实现对这些期货/任务究竟做了什么,以及这些对象的价值应该是什么。
  • 任务只能等待其他任务/未来。 - 请注意,这仅适用于 asyncio。 Triocurio 完全能够等待既不是任务也不是未来的对象——事实上,它们(故意)一开始就没有暴露这些抽象。
【解决方案2】:

语言不关心你返回哪个迭代器。错误来自 library asyncio,它对迭代器必须生成的值的种类有特定的想法。 Asyncio 需要 __await__ 来生成 asyncio 期货(包括它们的子类型,例如任务)或 None。其他库,如 curio 和 trio,将期望不同类型的值。总的来说,异步库不会记录他们对 __await__ 的期望,因为他们认为这是一个实现细节。

就 asyncio 而言,除了协程之外,您还应该使用更高级别的构造,例如期货和任务,并等待这些构造。很少需要手动实现__await__,即使then 你也应该使用它来委托另一个等待的信号。编写一个 __await__ 来创建并产生自己的新挂起值要求它与事件循环耦合并了解其内部结构。

您可以将__await__ 视为编写类似于asyncio 的库的工具。如果你是这样一个库的作者,那么当前的规范就足够了,因为你可以从迭代器中产生任何你喜欢的东西,只有你的事件循环中的代码会观察到产生的值。如果你不在那个位置,你可能不需要实现__await__

【讨论】:

  • 这样就搞定了。尽管编写该规范的人至少可以按照“此迭代器预期的值特定于事件循环实现”的方式添加一些内容,特别是关于asyncio 的注释(即“仅从@987654331 @tasks、futures 和 coroutines”)。
  • @Anakhand 我同意这种观点,尽管指定它比看起来更难,因为await 是一种非常通用的机制,可用于控制流not tied to 一个经典的事件循环。但即便如此,等待的对象总是需要 ,some 类型的驱动程序。我知道他们不会特别提到 asyncio,因为它当时可能仍处于临时状态。
猜你喜欢
  • 2012-03-28
  • 2014-07-25
  • 2010-11-16
  • 2012-01-21
  • 1970-01-01
相关资源
最近更新 更多