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

时间:2019-07-15 18:26:32

标签: python python-3.x generator-expression

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

那么,为什么这确实发生了呢?我敢肯定我遗漏了许多细节。

3 个答案:

答案 0 :(得分:8)

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

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

>>> 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

答案 1 :(得分:6)

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

答案 2 :(得分: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]