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

时间:2019-09-11 20:32:31

标签: python python-3.x async-await generator coroutine

我正在从老式的协程过渡(其中,“ 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

如果现在我创建和初始化协程对象,则可以向其发送编号,它将返回运行中的对象 平均:

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

...等等。问题是,如何用异步/等待方式编写这样的协程?那里 有三点让我感到困惑。我理解正确吗?

1)在旧样式中,任何人都可以将数字发送到平均器的同一实例。我可以通过 围绕上述值coro_avg并每次调用.send(N)时,无论从何处,N都添加到同一运行中 总。但是,使用异步/等待,就无法“发送值”。每次您“等待” 协程等待具有其上下文和变量值的新实例。

2)看来,“异步def”协程将值交还给正在等待的事物的唯一方法 它是“返回”并因此失去背景。您不能从“异步”内部调用“收益” def coroutine(或者,如果您这样做,则创建了一个异步生成器, 不能与await一起使用)。因此,“异步def”协程无法计算值并手动处理 像平均器一样在保持上下文的同时将其删除。

3)与(1)几乎相同:协程调用'await'时,它将等待一个特定的,可等待的, 即等待的论点。这与旧式协程非常不同,后者会放弃控制并 坐在周围等待任何人向他们发送东西。

我意识到新的协程与旧的协程是不同的编码范例: 带有事件循环,并且您使用队列之类的数据结构来使协程发出一个没有 返回和丢失上下文。不幸的是,新旧共享相同的概念有点令人困惑 名称-协程--鉴于它们的调用/返回协议是如此不同。

1 个答案:

答案 0 :(得分:0)

可以很直接地关联两个模型,这可能是有启发性的。实际上,现代协程与旧协程一样,是根据(通用)迭代器协议实现的。区别在于,迭代器的返回值会通过任意数量的协程调用程序自动向上传播(通过隐式yield from),而实际的返回值将打包为StopIteration异常。

此编排的目的是向驾驶员(假定的“事件循环”)告知可以恢复协程的条件。该驱动程序可以从一个无关的堆栈帧中恢复协程,并且可以通过等待的对象将数据发送回执行中,因为它是驱动程序唯一的已知通道,同样,send可以通过{进行透明通信{1}}。

此类双向通信的示例:

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=f(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)) 左右,则很容易。