主题包含整个想法。我遇到了一个代码示例,其中显示了类似以下内容:
async for item in getItems():
await item.process()
还有其他代码如下:
for item in await getItems():
await item.process()
这两种方法有明显的区别吗?
主题包含整个想法。我遇到了一个代码示例,其中显示了类似以下内容:
async for item in getItems():
await item.process()
还有其他代码如下:
for item in await getItems():
await item.process()
这两种方法有明显的区别吗?
虽然它们两者理论上可以使用相同的对象(不会导致错误),但它们很可能并不这样做。一般来说,这两种表示法根本不等价,而是调用完全不同的协议,并应用于非常不同的用例。
要理解它们之间的区别,首先需要了解 可迭代对象 的概念。
抽象地说,如果一个对象实现了 __iter__
方法或者(较少用于迭代)类似序列的 __getitem__
方法,则该对象是可迭代的。
实际上,如果您可以在for
循环中使用对象,则该对象是可迭代的,因此for _ in iterable
。 for
循环隐式调用可迭代对象的__iter__
方法,并期望其返回一个迭代器,该迭代器实现__next__
方法。该方法在for
循环的每次迭代开始时被调用,其返回值是分配给循环变量的值。
async
世界引入了一种变体,即异步可迭代对象。
如果一个对象实现了__aiter__
方法,则该对象是异步可迭代的。
从实际角度来看,如果一个对象可以在async for
循环中使用,例如async for _ in async_iterable
,那么它就是异步可迭代的。 async for
循环调用异步可迭代的__aiter__
方法,并期望它返回一个异步迭代器,该迭代器实现了__anext__
协程方法。该方法在每次async for
循环迭代开始时被等待。
一般来说,异步可迭代对象不是可等待的,也就是说它不是一个协程,也没有实现__await__
方法,反之亦然。虽然它们不一定是互斥的。你可以设计一个既可以自己等待,又可以(异步)迭代的对象,但这似乎是一个非常奇怪的设计。
为了在使用术语时非常清楚,迭代器是迭代对象的子类型。这意味着迭代器通过提供__iter__
方法来实现可迭代协议,但它还提供了__next__
方法。类似地,异步迭代器是异步可迭代对象的子类型,因为它实现了__aiter__
方法,但也提供了__anext__
协程方法。
你不需要对象成为迭代器就能在 for
循环中使用它,你需要的是它返回一个迭代器。你可以在 (async
) for
循环中使用 (异步) 迭代器的原因是它同时也是 (异步) 可迭代的。一个可迭代但不是迭代器的情况很少见。在大多数情况下,对象将同时是两者(即后者)。
async for _ in get_items()
这段代码意味着get_items
函数返回的任何内容都是异步可迭代对象。
请注意,get_items
只是一个普通的非async
函数,但它返回的对象实现了异步可迭代协议。这意味着我们可以写成以下形式:
async_iterable = get_items()
async for item in async_iterable:
...
for _ in await get_items()
这段代码表明get_items
实际上是一个协程函数(即可调用的返回awaitable的函数),而该协程的返回值是一个普通的可迭代对象。
请注意,我们可以确定get_items
协程返回的对象是一个普通的可迭代对象,否则常规的for
循环将无法使用它。等价的代码如下:
iterable = await get_items()
for item in iterable:
...
这些代码片段的另一个影响是,在第一个代码片段中,函数(返回异步迭代器)是非异步的,即调用它不会将控制权交给事件循环,而每个async for
-loop的迭代都是异步的(因此允许上下文切换)。
相反,在第二个代码片段中,返回普通迭代器的函数是一个异步调用,但所有的迭代(对__next__
的调用)都是非异步的。
实际应用的结论应该是,你展示的这两个代码片段永远不会等价。主要原因是get_items
要么是协程函数,要么不是。如果它不是,你就不能执行await get_items()
。但是,无论get_items
返回什么,你是否可以执行async for
或for
都取决于它。
为了完整起见,值得注意的是,上述协议的组合是完全可行的,尽管并不是很常见。考虑以下示例:
from __future__ import annotations
class Foo:
x = 0
def __iter__(self) -> Foo:
return self
def __next__(self) -> int:
if self.x >= 2:
raise StopIteration
self.x += 1
return self.x
def __aiter__(self) -> Foo:
return self
async def __anext__(self) -> int:
if self.x >= 3:
raise StopAsyncIteration
self.x += 1
return self.x * 10
async def main() -> None:
for i in Foo():
print(i)
async for i in Foo():
print(i)
if __name__ == "__main__":
from asyncio import run
run(main())
Foo
实现了四个不同的协议:
def __iter__
)def __next__
)def __aiter__
)async def __anext__
)main
协程将输出以下内容:1
2
10
20
30
from collections.abc import AsyncIterable, Iterable
def get_items_sync() -> AsyncIterable[int]:
return Foo()
async def get_items_async() -> Iterable[int]:
return Foo()
async def main() -> None:
async for i in get_items_sync():
print(i)
for i in await get_items_async():
print(i)
async for i in await get_items_async():
print(i)
if __name__ == "__main__":
from asyncio import run
run(main())
输出:
10
20
30
1
2
10
20
30
这非常清楚地说明了决定调用我们的Foo
方法(__next__
或__anext__
)的唯一因素是我们使用for
循环还是async for
循环。
for
循环将始终至少调用一次__next__
方法,并在每次迭代时继续调用它,直到拦截到StopIteration
异常。
async for
循环将始终至少等待一次__anext__
协程,并在每个后续迭代中继续调用和等待它,直到拦截到StopAsyncIteration
异常。
它们是完全不同的。
如果getItems()
是异步迭代器或异步生成器,那么这个for item in await getItems()
将无法工作(会抛出错误),它只能用于getItems
是协程的情况下,而在您的情况下,它应该返回一个序列对象(简单可迭代对象)。
async for
是一种传统的(也是Pythonic的)异步迭代器/生成器的方式。