`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
的情况下产生 Awaitable
s 因为内部函数是 async
.
- 在功能方面,
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.
- 因为外部
async
不需要产生 Awaitable
s,__anext__
是否提供同步 __next__
返回 Awaitable
s 的独占功能?
这是我可能遗漏的地方,但根据我目前的理解,异步协议和 StopAsyncIteration
看起来可以使用同步协议和 StopIteration
来模仿它们(不太简洁当然)。
您遗漏的一个大问题是如何处理循环结束。
一个异步迭代器的 __anext__
returns 一个可等待对象,它可以暂停,用一个值引发 StopIteration
来产生下一个元素,或者引发 StopAsyncIteration
来发出信号循环结束。 (PEP 说“要停止迭代 __anext__
必须引发 StopAsyncIteration 异常。”,但 StopAsyncIteration 异常确实发生在等待可等待对象时,而不是在调用 __anext__
时同步发生。)
相比之下,如果您尝试使用可等待元素创建一个常规可迭代对象,则迭代器的 __next__
需要引发 StopIteration
以结束循环。
这意味着 __next__
不能 return 直到它 知道 是否会有另一个元素。 __next__
是同步的,所以在解决这个问题时,控制不能 return 到事件循环。这意味着您可能会浪费大量时间同步等待网络流量或其他事情,而所有其他工作都停滞不前。
你可以通过更多的手动处理来解决这个问题,但它在两端都变得很尴尬,尤其是迭代器的一端,你需要某种等同于 StopAsyncIteration 的东西来消除“这是下一个元素”和“循环的”的歧义完成了。
采用以下两个函数:
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
的情况下产生 Awaitable
s 因为内部函数是 async
.
- 在功能方面,
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.
- 因为外部
async
不需要产生Awaitable
s,__anext__
是否提供同步__next__
返回Awaitable
s 的独占功能?
这是我可能遗漏的地方,但根据我目前的理解,异步协议和 StopAsyncIteration
看起来可以使用同步协议和 StopIteration
来模仿它们(不太简洁当然)。
您遗漏的一个大问题是如何处理循环结束。
一个异步迭代器的 __anext__
returns 一个可等待对象,它可以暂停,用一个值引发 StopIteration
来产生下一个元素,或者引发 StopAsyncIteration
来发出信号循环结束。 (PEP 说“要停止迭代 __anext__
必须引发 StopAsyncIteration 异常。”,但 StopAsyncIteration 异常确实发生在等待可等待对象时,而不是在调用 __anext__
时同步发生。)
相比之下,如果您尝试使用可等待元素创建一个常规可迭代对象,则迭代器的 __next__
需要引发 StopIteration
以结束循环。
这意味着 __next__
不能 return 直到它 知道 是否会有另一个元素。 __next__
是同步的,所以在解决这个问题时,控制不能 return 到事件循环。这意味着您可能会浪费大量时间同步等待网络流量或其他事情,而所有其他工作都停滞不前。
你可以通过更多的手动处理来解决这个问题,但它在两端都变得很尴尬,尤其是迭代器的一端,你需要某种等同于 StopAsyncIteration 的东西来消除“这是下一个元素”和“循环的”的歧义完成了。