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



我正在从旧式协程过渡(其中"yield"返回"发送"提供的值,但是 否则本质上是生成器(到具有"异步定义"和"等待"的新型协程。 有几件事真的让我感到困惑。

考虑以下旧式协程,该协程计算提供给 它通过"发送",在每个点返回到目前为止的平均值。(这个例子来自《流利》第16章 LucianoRamalho的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,N 都会添加到同一个运行中 总。但是,使用 async/await 时,无法"发送值"。每次你"等待"一个 协程 您等待具有自己的上下文、自己的变量值的新实例。

2(似乎"异步定义"协程将值交还给等待的东西的唯一方法 它是"返回",因此失去了上下文。您不能从"异步"内部调用"产量" def' 协程(或者更确切地说,如果你这样做,你已经创建了一个异步生成器, 不能与 await 一起使用(。因此,"异步定义"协程无法计算值和手 它在保持上下文的同时,就像 Averager 所做的那样。

3(几乎与(1(相同:当协程调用"await"时,它会等待单个特定的等待, 即等待的论点。这与旧式协程非常不同,旧式协程放弃了控制和 坐在那里等待任何人向他们发送东西。

我意识到新的协程是一种不同于旧协程的编码范式:它们被使用 使用事件循环,并且使用队列等数据结构让协程发出一个值,而无需 返回和丢失上下文。新旧共享相同,这有点不幸和有些令人困惑 名称---协程---鉴于它们的调用/返回协议如此不同。

将这两个模型直接联系起来是可能的,也许是有启发性的。 现代协程实际上是在(广义(迭代器协议方面实现的,就像旧的一样。 不同之处在于,来自迭代器的返回值通过任意数量的协程调用器(通过隐式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左右,这很容易。

最新更新