如何在Python中使用`async for`?

时间:2019-05-16 05:50:00

标签: python asynchronous python-asyncio

我的意思是我从使用async for中学到什么。这是我用async for编写的代码,AIter(10)可以替换为get_range()

但是代码的运行方式类似于同步而不是异步。

import asyncio

async def get_range():
    for i in range(10):
        print(f"start {i}")
        await asyncio.sleep(1)
        print(f"end {i}")
        yield i

class AIter:
    def __init__(self, N):
        self.i = 0
        self.N = N

    def __aiter__(self):
        return self

    async def __anext__(self):
        i = self.i
        print(f"start {i}")
        await asyncio.sleep(1)
        print(f"end {i}")
        if i >= self.N:
            raise StopAsyncIteration
        self.i += 1
        return i

async def main():
    async for p in AIter(10):
        print(f"finally {p}")

if __name__ == "__main__":
    asyncio.run(main())

我排除的结果应该是:

start 1
start 2
start 3
...
end 1
end 2
...
finally 1
finally 2
...

但是,实际结果是:

start 0
end 0
finally 0
start 1
end 1
finally 1
start 2
end 2

我知道我可以通过使用asyncio.gatherasyncio.wait得到例外结果。

但是我很难理解这里使用async for而不是简单的for所得到的结果。

如果我想循环几个async for对象并在一个对象完成后立即使用它们,那么使用Feature的正确方法是什么?例如:

async for f in feature_objects:
    data = await f
    with open("file", "w") as fi:
        fi.write()

1 个答案:

答案 0 :(得分:4)

  

但是我很难理解这里使用async for而不是简单的for所得到的结果。

潜在的误解是您似乎期望async for自动对迭代进行并行化。它不会这样做,它只是允许在异步源上进行顺序迭代 。例如,您可以使用async for遍历TCP流中的行,Websocket中的消息或异步DB驱动程序中的数据库记录。

您不能使用普通的for来执行上述任何操作,至少不能不阻塞事件循环,因为for__next__作为阻塞函数进行调用,并且不会等待其执行结果。您无法通过手动等待每个元素来进行补偿,因为for期望__next__通过引发异常来表示迭代结束-如果__next__是协程,则该异常在之前不会可见等待它。这就是为什么async for不仅在Python中被引入,而且在other languages中被引入async / await和广义for的原因。

如果要并行运行迭代,则需要将它们作为并行协程启动,并使用asyncio.as_completed或同等功能来检索它们来的结果:

async def x(i):
    print(f"start {i}")
    await asyncio.sleep(1)
    print(f"end {i}")
    return i

for f in asyncio.as_completed([x(i) for i in range(10)]):
    result = await f
    # ... do something with the result ...