Python中oldstyle和newstyle协程的调用/返回协议有什么区别?

2024-06-12 00:52:15 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在从旧式的协程转换(其中'yield'返回'send'提供的值,但是 它们本质上是生成器)到带有“async def”和“await”的新型协程。 有几件事让我很困惑。你知道吗

考虑下面的旧式协程,它计算提供给用户的数字的运行平均值 它通过“发送”,在每个点返回到目前为止的平均值。(这个例子来自Fluent的第16章 Python(Luciano Ramalho)

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

如果我现在创建并初始化一个coroutine对象,我可以给它发送数字,它将返回运行状态 平均值:

>>> 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)在旧样式中,任何人都可以将数字发送到averager的同一个实例。我可以通过 每次调用.send(N)时,无论从何处,都会将N添加到相同的运行中 总计。但是,使用async/await时,无法“发送值”。每次你“等待”一个 coroutine您将等待一个具有自己上下文和变量值的新实例。你知道吗

2)“async def”协程将值交回等待的对象似乎是唯一的方法 它是“返回”,因此失去上下文。不能从“async”内部调用“yield” def'coroutine(或者更确切地说,如果您已经创建了一个异步生成器 不能与wait一起使用)。所以一个“async def”协程不能计算一个值并手动执行 就像averager一样,在维护上下文的同时,它也会被删除。你知道吗

3)几乎与(1)相同:当一个协程调用'await'时,它会等待一个单独的、特定的awaitable, 即等待的论据。这与旧式的协同程序非常不同,后者放弃了控制和控制 坐在那里等着任何人给他们寄东西。你知道吗

我意识到新的协同程序是与旧的不同的编码范式:它们被使用 对于事件循环,您可以使用队列之类的数据结构,使协同程序在不使用 返回并失去上下文。新的和旧的都是一样的,这有点不幸,也有点混乱 名称---协同程序---因为它们的调用/返回协议非常不同。你知道吗


Tags: sendasyncdefcount数字awaitavg平均值
1条回答
网友
1楼 · 发布于 2024-06-12 00:52:15

把这两个模型直接联系起来是可能的,也许是有益的。现代协同程序实际上是按照(通用的)迭代器协议实现的,就像旧的一样。不同之处在于,迭代器的返回值通过任意数量的协程调用程序自动向上传播(通过隐式的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左右的地方,就很容易了。你知道吗

相关问题 更多 >