Python 中旧式协程和新式协程的调用/返回协议有什么区别?

2024-02-08

我正在从旧式协程过渡(其中“yield”返回“send”提供的值,但是 本质上是生成器)到带有“async def”和“await”的新型协程。 有几件事确实让我困惑。

考虑以下旧式协程,它计算提供给的数字的运行平均值 它通过“发送”,在每个点返回到目前为止的平均值。 (这个例子来自《流利 Python作者:卢西亚诺·拉马略。)

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
         term = yield average
         total += term
         count += 1
         average = total/count

如果我现在创建并启动一个协程对象,我可以向它发送数字,它将返回正在运行的 平均的:

>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0

...等等。问题是,这样的协程如何用async/await来写呢?那里 有三点让我困惑。我对它们的理解正确吗?

1) 在旧方式中,任何人都可以将数字发送到平均器的同一实例。我可以通过 在上面的值 coro_avg 周围,每次调用 .send(N) 时,无论从哪里,N 都会添加到同一个运行中 全部的。然而,使用 async/await 时,无法“发送值”。每次你“等待”一个 在协程中,您等待一个具有自己的上下文和变量值的新实例。

2)似乎“async def”协程将值返回给正在等待的事物的唯一方法 它是“返回”,因此失去了上下文。您不能从“异步”内部调用“yield” def' 协程(或者更确切地说,如果你这样做了,你已经创建了一个异步生成器 不能与await 一起使用)。因此“async def”协程无法计算值和手 就像平均器一样,在保持上下文的同时将其输出。

3) 与 (1) 几乎相同:当协程调用“await”时,它会等待单个特定的可等待对象, 即等待的参数。这与旧式协程非常不同,旧式协程放弃控制并 坐着等待anyone发送一些东西给他们。

我意识到新的协程是与旧的协程不同的编码范例:它们被使用 使用事件循环,您可以使用队列等数据结构让协程发出一个值,而无需 返回和失去上下文。新的和旧的共享相同的东西,这有点不幸,也有点令人困惑 名称——协程——因为它们的调用/返回协议是如此不同。


将这两个模型非常直接地联系起来是可能的,也许也是有启发性的。现代协程实际上是按照(通用)迭代器协议实现的,就像旧协程一样。不同之处在于,迭代器的返回值会通过任意数量的协程调用者自动向上传播(通过隐式yield from),而实际返回值被打包成StopIteration例外情况。

这种编排的目的是通知驱动程序(假定的“事件循环”)可以恢复协程的条件。该驱动程序可以从不相关的堆栈帧恢复协程,并可以通过等待的对象将数据发送回执行,因为它是驱动程序已知的唯一通道,同样如此send通过透明地通信yield from.

此类双向通信的示例:

class Send:
  def __call__(self,x): self.value=x
  def __await__(self):
    yield self  # same object for awaiter and driver
    raise StopIteration(self.value)

async def add(x):
  return await Send()+x

def plus(a,b):  # a driver
  c=add(b)
  # Equivalent to next(c.__await__())
  c.send(None)(a)
  try: c.send(None)
  except StopIteration as si: return si.value
  raise RuntimeError("Didn't resume/finish")

真正的司机当然会决定调用结果send只有在认识到它是Send.

实际上,您不想自己驱动现代协程;您想自己驱动现代协程。它们针对完全相反的方法进行了语法优化。然而,使用队列来处理一个方向的通信是很简单的(正如您已经注意到的):

async def avg(q):
  n=s=0
  while True:
    x=await q.get()
    if x is None: break
    n+=1; s+=x
    yield s/n

async def test():
  q=asyncio.Queue()
  i=iter([10,30,5])
  await q.put(next(i))
  async for a in avg(q):
    print(a)
    await q.put(next(i,None))

以这种方式提供价值观有点痛苦,但如果它们来自另一个人,那就很容易Queue or so.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 中旧式协程和新式协程的调用/返回协议有什么区别? 的相关文章

随机推荐