Python:如何迭代列表或异步生成器?

9

自从迭代器在Python中被引入,我们就可以不必关心正在处理的是迭代器还是列表:

from random import random

def gen_list():
    print('gen')
    for i in range(10):
        yield i

def return_list():
    print('return')
    return [i for i in range(10)]


if random() > 0.5:
    x = gen_list()
else:
    x = return_list()

for i in x:
    pass

PEP 492引入了异步迭代器async for语法。但我并没有看到对于增加异步迭代器消费者语法的新负担有任何正当理由。

在我的代码中,我有时会处理来自缓存的列表,有时会处理异步生成器:

import asyncio
from random import random

def is_small_and_in_cache():
    if random() > 0.5:
        print('in fake cache')
        return [i for i in range(10)]

async def get_progressively():
    print('gen')
    for i in range(10):
        # e.g. an await here
        await asyncio.sleep(0.1)
        yield i

async def main():
    x = is_small_and_in_cache()
    if x is None:
        x = get_progressively()

    async for i in x:
        pass

asyncio.run(main())

然而上述方法会有一半的时间失败,出现 TypeError: 'async for' requires an object with __aiter__ method, got list 错误。

主要问题:如何编写代码以应对两种情况?是将列表转换为虚拟异步生成器,还是封装异步生成器以产生列表?

次要问题:是否有任何提案来摆脱(对我来说显然不符合Python风格的)async for 结构?也就是说,为什么常规的 for 循环不能处理异步生成器呢?在可用性方面,Python3.x 是否已经迷失了自己的方向?

1个回答

8

这种语法存在的目的是为了警告你,你的“循环”可能实际上包括暂停整个调用,允许其他代码运行,因此你需要确保每次迭代在顶部具有适当的数据以保持一致状态。它没有任何变化。

当然,协程不一定需要暂停,你可以利用这一点使封装任何可迭代对象变得非常简单:

async def desync(it):
  for x in it: yield x

这比相反的操作更加普遍适用,相反的操作需要异步地将内容收集到列表中:

async def gather(ai):
  ret=[]
  async for x in ai: ret.append(x)
  return ret

由于它允许在完全异步的情况下进行适当地交错处理。


嗯,虽然这是学术性的,但 for i in await x: 更能传达这个想法,因为它不是循环放弃控制,而是生成器。这也更好地反映了现有异步函数系统的调用者=>await/callee=>async。 - EoghanM
@EoghanM: for i in await x: 已经有了明确的含义:x 是某个可等待对象(例如,一个已暂停的异步生成器),可以一次性提供一个完整的(同步)可迭代对象。 - Davis Herring

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