这个主题包含了整个想法。我看到了一个代码示例,它显示了如下内容:
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()
这两种方法是否有显著差异?
2条答案
按热度按时间wrrgggsh1#
靶区; DR
虽然它们 * 理论上 * 可以使用同一个对象(而不会导致错误),但它们很可能不。一般来说,这两种表示法根本不等价,但调用完全不同的协议,并应用于非常不同的用例。
不同的协议
可迭代
要理解这种区别,首先需要理解可迭代的概念。
抽象地说,如果一个对象实现了
__iter__
方法或(迭代中不太常见的)类似序列的__getitem__
方法,那么它就是可迭代的。实际上,一个对象是iterable,如果你可以在
for
-循环中使用它,那么for _ in iterable
.for
-循环隐式调用iterable的__iter__
方法,并期望它返回一个iterator,它实现了__next__
方法,该方法在for
-循环中每次迭代开始时调用,其返回值是分配给循环变量的值。异步可迭代
async
-world引入了它的一个变体,即异步可迭代。如果一个对象实现了
__aiter__
方法,那么它是异步可迭代的。同样,实际上讲,一个对象是异步可迭代的,如果它可以在
async for
-循环中使用,那么async for _ in async_iterable
.async for
-循环调用异步可迭代对象的__aiter__
方法,并期望它返回一个异步迭代器,它实现了__anext__
协程方法。该方法在async for
循环的每次迭代开始时等待。等待
一般来说,异步可迭代对象是不可等待的,也就是说,它不是一个协程,它不实现
__await__
方法,反之亦然。尽管它们不一定是互斥的。你可以设计一个对象,它本身是可等待的,也是(异步)可迭代的,尽管这看起来是一个非常奇怪的设计。(异步)迭代器
为了使所使用的术语更加清楚,迭代器tor是迭代器ble的子类型。这意味着迭代器还通过提供
__iter__
方法来实现可迭代协议,但它也提供__next__
方法。类似地,异步迭代器是异步可迭代的子类型,因为它实现了__aiter__
方法。而且提供了__anext__
协程方法。你不需要这个对象是一个迭代器,就可以在
for
循环中使用它,你需要它来 * return * 一个迭代器。事实上你可以使用(异步)迭代器(async
)for
-循环是因为它也是一个(异步)可迭代的。很少有东西是可迭代的,但 * 不是 * 迭代器。在大多数情况下,对象将是两者(即后者)。从你的例子中推断
async for _ in get_items()
这段代码意味着
get_items
函数 * 返回 * 的任何内容都是异步可迭代对象。注意,
get_items
只是一个普通的非async
函数,但是它返回的对象实现了异步可迭代协议,这意味着我们可以编写如下代码:for _ in await get_items()
然而这个代码段暗示
get_items
* 实际上是一个协程函数(即返回一个可等待的可调用函数),并且该协程的返回值是一个正常的可迭代函数。注意我们知道
get_items
协程返回的对象是一个 * normal * 可迭代对象,否则常规的for
-循环将无法使用它。暗示
这些代码片段的另一个含义是,在第一个中,函数(返回异步迭代器)是非异步的,也就是说,调用它不会产生对事件循环的控制,而
async for
-循环的每个迭代都是异步的(因此将允许上下文切换)。相反,在第二个例子中,返回普通迭代器的函数是一个异步调用,但是所有的迭代(对
__next__
的调用)都是非异步的。密钥差异
实际上,你展示的这两个代码片段是 * 从不 * 等价的。主要原因是
get_items
要么是要么不是协程函数。如果它不是 *,你就不能执行await get_items()
。但是你是否能执行async for
或for
取决于get_items
返回的内容。可能的组合
为了完整起见,应该注意到上述协议的组合是完全可行的,尽管不是太常见。
在此示例中,
Foo
实现了四种不同的协议:def __iter__
)def __next__
)def __aiter__
)async def __anext__
)运行
main
协程会得到以下输出:这表明对象完全可以同时是所有这些东西,因为
Foo
既是同步的又是异步的可迭代对象,所以我们可以编写两个函数--一个协程,一个常规--每个函数返回Foo
的一个示例,然后稍微复制一下您的示例:输出:
这非常清楚地表明,唯一决定调用
Foo
方法(__next__
或__anext__
)的是我们使用for
-循环还是async for
-循环。for
-循环将总是至少调用__next__
方法一次,并在每次迭代中继续调用它,直到它截获一个StopIteration
异常。async for
-循环将总是等待__anext__
协程至少一次,并继续调用和等待它的每个后续迭代,直到它拦截到一个StopAsyncIteration
异常。mlmc2os52#
那是完全不同的。
如果
getItems()
是一个异步迭代器或异步生成器,那么for item in await getItems()
将不起作用(将抛出一个错误),只有当getItems
是一个协程时才可以使用它,在您的情况下,协程应该返回一个序列对象(简单可迭代)。async for
是用于在 async 迭代器/生成器上进行异步迭代的常规(和Python)方式。