异步生成器不是迭代器?

42

在Python中,您可以编写一个可迭代的生成器,例如:

def generate(count):
    for x in range(count):
        yield x

# as an iterator you can apply the function next() to get the values.
it = generate(10)
r0 = next(it)
r1 = next(it) ...

当尝试使用异步迭代器时,会出现“在异步函数内不能使用yield”的错误。建议解决方案是实现您自己的生成器:

class async_generator:
    def __aiter__(self):
        return self

    async def __anext__(self):
        await asyncio.sleep()
        return random.randint(0, 10)
        
# But when you try to get the next element
it = async_generator(10)
r0 = next(it)

你遇到了错误"async_generator"对象不是迭代器

我认为,如果你将某个东西称为迭代器,那么它必须具有完全相同的接口,因此我可以编写异步迭代器并在一个大量依赖于next()调用的框架上使用。

如果您需要重写整个代码才能使用异步功能,则任何新的Python功能都是无意义的。

我错过了什么吗?

谢谢!

4个回答

37

所以,就像@bosnjak说的那样,你可以使用async:

async for ITEM in A_ITER:
    BLOCK1
else: # optional
    BLOCK2

但是如果您想手动迭代,只需编写:

it = async_iterator()
await it.__anext__()

但我不建议这样做。

我认为如果你要称呼某个东西为迭代器,那就是因为它有完全相同的接口,所以我可以编写异步迭代器并在一个严重依赖于 next() 调用的框架上使用它。

不,实际上它们并不相同。常规同步迭代器和异步迭代器之间存在差异。以下是一些原因:

  1. Python 协程内部是基于生成器构建的
  2. 根据 Python 之禅,显式优于隐式。所以你实际上会看到代码可以在哪里被挂起。

这就是为什么无法使用 iternext 来遍历异步迭代器。而且你不能将其用于期望同步迭代器的框架中。因此,如果你要使代码变成异步的,你必须同时使用异步框架。 这里有几个第三方异步框架。

此外,我想说几句有关迭代器和生成器的话。迭代器是一个特殊的对象,有 __iter____next__ 方法。而生成器是一个包含 yield 表达式的特殊函数。每个生成器都是迭代器,但反之则不成立。异步迭代器和生成器也同样适用于这个规则。是的,自 Python 3.6 以来,你可以编写异步生成器!

async def ticker(delay, to):
    for i in range(to):
        yield i
        await asyncio.sleep(delay)

您可以阅读PEP 525了解更多细节。


2
你说每个生成器都是迭代器,这也适用于异步生成器。那么为什么Python会抱怨async_generator不是迭代器呢? - fenceop
2
但是为什么我可以使用 ''.join([x async for x in xs]) 而不能使用 ''.join(x async for x in xs)?这只是 [] 对异步生成器有特殊的语法吗? - Thomas Ahle
1
在Python 3.6中,增加了异步理解的新语法(PEP 530)。因此,您可以编写[x async for x in xs]。而(x for x in xs)是生成器表达式(将另外一对括号简单省略后,可将其作为函数参数传递)。我认为您无法使用异步代码创建生成器对象。因此,(x async for x in xs)是无效语法。 - Michael Ihnatenko
1
Python 3.10(终于)引入了aiter()anext(),无需调用__aiter____anext__。https://docs.python.org/3/library/functions.html#anext - Philip Couling

8

我相信对于异步生成器,已经引入了一个新的语句:

async for TARGET in ITER:
    BLOCK
else:
    BLOCK2

根据PEP 492,基本上意味着您应该执行以下操作:

async for number in generate(10):
        print(number)

此外,请查看与生成器的不同之处
原生协程对象不实现iternext方法。因此,它们不能被迭代或传递给iter()、list()、tuple()和其他内置函数。它们也不能在for..in循环中使用。试图在原生协程对象上使用iter或next将导致TypeError。

那么,我必须假设没有异步迭代器这样的东西?只有异步生成器? - user1275011
这就是我所知道的,但我不是专家,特别是在异步方面。 - bosnjak

4
在Python 3.10+中,可以使用anexthttps://docs.python.org/3/library/functions.html#anext。 我修改了之前的示例来说明anext的工作原理:
import asyncio


async def ticker(to):
    for i in range(to):
        await asyncio.sleep(1)
        yield i


async def main():

    # get only first, failed if nothing yield
    await anext(ticker(3))

    # get only first or default (77)
    await anext(ticker(3), 77)

    # iterate through all
    async for i in ticker(3):
        print(i)

asyncio.run(main())

1
我使用这个来异步循环遍历列表。
class AsyncRange(object):
    def __init__(self, length):
        self.length = length
        self.i = 0

    async def __aiter__(self):
        return self

    async def __anext__(self):
        index = self.i
        self.i += 1
        if self.i <= self.length:
            return index
        else:
            raise StopAsyncIteration

然后简单地:
async for i in AsyncRange(my_list):
    # your code

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