`AsyncIterable[T]` 和 `Iterable[Awaitable[T]]` 之间的区别?

Difference between `AsyncIterable[T]` and `Iterable[Awaitable[T]]`?

采用以下两个函数:

import asyncio

def a():
    for index in range(2):
        # Capture `index` as `local_index` in case awaiting is postponed.
        async def next_index(local_index=index):
            # Simulate network request.
            await asyncio.sleep(0)

            return local_index

        yield next_index()

async def b():
    for index in range(2):
        # Simulate network request.
        await asyncio.sleep(0)

        yield index

a returns 一个 Iterable[Awaitable[int]]b returns 一个 AsyncIterable[int]。两者的迭代可以像这样完成:

async def main():
    for index in a():
        print(await index)

    async for index in b():
        print(index)

asyncio.run(main())

输出:

0
1
0
1

上面例子的关键是我能够在没有外部 async 的情况下产生 Awaitables 因为内部函数是 async.

  1. 在功能方面,AsyncIterable[T] 是否允许超过 Iterable[Awaitable[T]]

我也有一个非常相关的问题。来自 PEP 492 - Asynchronous Iterators and "async for":

An asynchronous iterator object must implement an anext method (or, if defined with CPython C API, tp_as_async.am_anext slot) returning an awaitable.

  1. 因为外部 async 不需要产生 Awaitables,__anext__ 是否提供同步 __next__ 返回 Awaitables 的独占功能?

这是我可能遗漏的地方,但根据我目前的理解,异步协议和 StopAsyncIteration 看起来可以使用同步协议和 StopIteration 来模仿它们(不太简洁当然)。

您遗漏的一个大问题是如何处理循环结束。

一个异步迭代器的 __anext__ returns 一个可等待对象,它可以暂停,用一个值引发 StopIteration 来产生下一个元素,或者引发 StopAsyncIteration 来发出信号循环结束。 (PEP 说“要停止迭代 __anext__ 必须引发 StopAsyncIteration 异常。”,但 StopAsyncIteration 异常确实发生在等待可等待对象时,而不是在调用 __anext__ 时同步发生。)

相比之下,如果您尝试使用可等待元素创建一个常规可迭代对象,则迭代器的 __next__ 需要引发 StopIteration 以结束循环。

这意味着 __next__ 不能 return 直到它 知道 是否会有另一个元素。 __next__ 是同步的,所以在解决这个问题时,控制不能 return 到事件循环。这意味着您可能会浪费大量时间同步等待网络流量或其他事情,而所有其他工作都停滞不前。

你可以通过更多的手动处理来解决这个问题,但它在两端都变得很尴尬,尤其是迭代器的一端,你需要某种等同于 StopAsyncIteration 的东西来消除“这是下一个元素”和“循环的”的歧义完成了。