将生成器表达式传递给 any() 和 all()

Passing generator expressions to any() and all()

我只是在 Python 解释器中乱搞,我遇到了一些意想不到的行为。

>>> bools = (True, True, True, False)
>>> all(bools)
False
>>> any(bools)
True

好的,到目前为止没有任何异常...

>>> bools = (b for b in (True, True, True, False))
>>> all(bools)
False
>>> any(bools)
False

事情开始变得诡异了。我认为发生这种情况是因为 all 函数遍历生成器表达式,调用它的 __next__ 方法并用完这些值,直到它遇到一个 False。以下是支持该理论的一些证据:

>>> bools = (b for b in (True, False, True, True))
>>> all(bools)
False
>>> any(bools)
True

我认为结果不同,因为False不在最后,所以生成器中仍然有一些未使用的值。如果你输入

>>> bools = (b for b in (True, False, True, True))
>>> all(bools)
False
>>> list(bools)
[True, True]

好像只剩下2个值了。

那么,为什么会发生这种情况呢?我确定我遗漏了很多细节。

您遇到的问题是您在生成所有值后使用生成器。

您可以通过运行以下代码验证这一点:

>>> bools = (b for b in (True, False, True, True))
>>> all(bools) # once the False is found it will stop producing values
True
>>> next(bools) # next value after False which is True
True
>>> next(bools) # next value after True which is True
True
>>> next(bools)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

这会起作用:

>>> bools = (b for b in (True, False, True, True))
>>> all(bools)
False
>>> bools = (b for b in (True, False, True, True))
>>> any(bools)
True

all() and any() 的行为记录在官方文档中。

来自pseudo-code:

def all(iterable):
    for element in iterable:
        if not element:
            return False
    return True

all() 只消耗 True 个元素,当它找到第一个计算结果为 False.

的元素时终止
def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

any() 仅消耗 False 个元素,当它找到第一个计算结果为 True.

的元素时终止

请注意,发电机在传递时不会重置到初始位置。 除非消耗更多物品,否则它们会停留在当前位置。因此,

>>> bools = (b for b in (True, False, True, True))

以下将消耗前两项。由于第二项是 False,迭代在此之后停止。这使生成器位于第二个元素之后的位置。

>>> all(bools)
False

此时生成器有 (True, True) 作为剩余值。您在问题中正确指出了这一点。以下只消耗单个元素

>>> any(bools)
True

请注意,还有一个 True 值可获得 调用 any().

后从生成器

当然,如果您在生成器上调用 list(),生成器中的所有项目都会被消耗掉,并且生成器将不再生成任何项目(它是 "empty")。

这里有几件事在起作用。

首先,生成器可以 运行 为每个给定的元素恰好一次。与列表、元组或任何其他具有固定状态的对象不同,生成器知道 __next__ 值是什么,之后如何生成该值,基本上什么都不知道。当你调用 next(generator) 时,你得到了下一个值,生成器计算出一个新的 __next__,并且它完全失去了你刚刚获得的值的记忆。本质上,生成器不能连续多次使用.

第二件事是 all()any()list() 如何在内部工作,尤其是 vis-a-vis 生成器。 all() 的实现看起来像这样,只是更复杂:

def all(iterable):
    for element in iterable:
        if bool(element) is False:
            return False
    return True

也就是说,all() 函数 short-circuits 当它第一次找到 non-truthy 元素时(并且 any() 执行同样的事情,除了相反)。这是为了节省处理时间——如果只有第一个元素是不可接受的,为什么还要处理其余的可迭代对象呢?对于生成器(例如您的最后一个示例),这意味着它会消耗所有元素,直到找到 False。生成器仍然有剩余的元素,但由于它已经生成了前两个元素,它在未来的行为就像它们从未存在过一样。

list() 更简单,只需调用 next(generator) 直到生成器停止生成值。这使得生成器放弃 它尚未消耗的任何值

所以你最后一个例子的解释是

  1. 您创建了一个生成器,它将按顺序 True, False, True, True 吐出元素
  2. 你在那个生成器上调用 all(),它在终止之前消耗了生成器的前两个元素,发现了一个错误的值。
  3. 您对该生成器调用 list(),它会消耗生成器的所有剩余元素(即最后两个元素)以创建一个列表。它产生 [2, 2].