Python异步推导式 - 它们是如何工作的?

40

我有困难理解Python 3.6中引入的异步推导式的使用。免责声明,我在Python中处理异步代码方面没有太多经验。

Python 3.6新特性文档中给出的示例是:

result = [i async for i in aiter() if i % 2]

PEP中,这被扩展为:
result = []
async for i in aiter():
    if i % 2:
        result.append(i)

我认为我理解了`aiter()`函数是异步调用的,这样每次迭代可以在前一个迭代返回之前进行(或者我的理解是错误的?)。
我不确定这如何转化到这里的列表推导式。结果是否按返回顺序放入列表中?或者在最终列表中有有效的“占位符”,以便将每个结果按正确顺序放入列表中?还是我想错了?
此外,能否提供一个真实世界的例子,说明适用的用例和async在这种推导式中的基本机制?

我也对异步生成器感到好奇。它们的行为相同还是不同? - Sebastian Wozny
2个回答

38
您基本上是在询问异步 for 循环如何与常规循环一起工作。您现在可以在列表推导式中使用这样的循环,但这并没有什么区别;这只是一种优化,避免了重复的 list.append() 调用,就像普通的列表推导式一样。
因此,async for 循环会等待迭代协议的每个下一步,而常规的 for 循环则会阻塞。
为了举例说明,请想象一个正常的 for 循环:
for foo in bar:
    ...

对于这个循环,Python基本上会执行以下操作:

bar_iter = iter(bar)
while True:
    try:
        foo = next(bar_iter)
    except StopIteration:
        break
    ...

next(bar_iter)调用不是异步的,它会阻塞。

现在将for替换为async for,Python的行为将发生变化:

bar_iter = aiter(bar)  # aiter doesn't exist, but see below
while True:
    try:
        foo = await anext(bar_iter)  # anext doesn't exist, but see below
    except StopIteration:
        break
    ...

在上面的示例中,aiter()anext()是虚构的函数;它们在功能上与其iter()next()兄弟函数完全等效,但使用__aiter____anext__而不是__iter____next__。也就是说,异步钩子为相同的功能存在,但通过前缀a与其非异步变体区分开来。
那里的await关键字是关键的区别,因此对于每次迭代,async for循环都会放弃控制权,以便其他协程可以运行。
再次重申,所有这些已经在Python 3.5中添加了(参见PEP 492),Python 3.6中新的是您也可以在列表推导式中使用这样的循环。对于生成器表达式、集合和字典推导式也是如此。
最后但并非最不重要的一点,同一组更改还使得在推导式的表达式部分中使用await <expression>成为可能,因此:
[await func(i) for i in someiterable]

现在可以实现。

感谢Martijn提供详细的答案。所以async for循环的行为与普通的for循环相同,只是循环迭代的控制权被传递给封闭的协程?我将不得不仔细审查协程的使用,但这更加合理。 - Andrew Guy

17

我认为aiter()函数是异步调用的,因此每次对aiter的迭代都可以在前一个迭代返回之前进行(这种理解是否正确?)。

那个理解是不正确的。使用async for循环进行的迭代不能并行执行。与普通的for循环一样,async for循环也是按顺序执行的。

async for循环的异步部分是让迭代器以协程正在迭代的方式await它。它仅供在异步协程内使用,并且仅供在特殊的异步可迭代对象上使用。除此之外,它基本上就像一个普通的for循环。


谢谢,看来在尝试理解async的使用之前,我需要去透彻理解协程。感谢您的指正。 :) - Andrew Guy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接