7

我只是在 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 个剩余值。

那么,为什么这真的会发生呢?我敢肯定有很多我遗漏的细节。

4

3 回答 3

9

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

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

>>> 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
于 2019-07-15T18:31:12.497 回答
8

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

从伪代码:

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()生成器,则生成器中的所有项目都将被消耗,并且生成器将不再产生任何项目(它是“空的”)。

于 2019-07-15T18:34:41.380 回答
3

这里有几件事在起作用。

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

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

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

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

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

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

  1. True, False, True, True您创建一个生成器,它将按顺序吐出元素
  2. 你调用all()那个生成器,它在它终止之前消耗了生成器的前两个元素,发现了一个错误的值。
  3. 您调用list()该生成器,它会使用生成器的所有剩余元素(即最后两个)来创建一个列表。它产生[2, 2].
于 2019-07-15T18:37:31.147 回答