我正在从老式的协程过渡(其中,“ 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'时,它将等待一个特定的,可等待的, 即等待的论点。这与旧式协程非常不同,后者会放弃控制并 坐在周围等待任何人向他们发送东西。
我意识到新的协程与旧的协程是不同的编码范例: 带有事件循环,并且您使用队列之类的数据结构来使协程发出一个没有 返回和丢失上下文。不幸的是,新旧共享相同的概念有点令人困惑 名称-协程--鉴于它们的调用/返回协议是如此不同。
答案 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))
左右,则很容易。