【问题标题】:Passing generator expressions to any() and all()将生成器表达式传递给 any() 和 all()
【发布时间】:2019-11-24 11:37:59
【问题描述】:

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

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

【问题讨论】:

  • 你用的是什么版本的 Python?
  • 只是为了详细说明正确答案:发电机排气。当所有值都生成后,它将不再生成。那么,使用相同的生成器两次,一开始就不会像您预期的那样工作。
  • 这其实是一个很好的问题。我从来没有想过这一点,现在我已经通过一些测试来理解这一点!

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


【解决方案1】:

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

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

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

【讨论】:

  • 不错的收获——这真的很难看到。
  • 第一个例子没有产生错误,它打印True。我想如果你把它改成bools = (b for b in (True, True, True, False),就会产生给定的错误。
  • 这个答案和@dhke 的答案都对生成器种子的不同组织有效
  • @samp-pyt,没错,我刚刚更新了我的答案。
【解决方案2】:

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

【讨论】:

  • 感谢@dhke 的精彩回答。我喜欢你解释函数内部发生的事情的方式。
  • @sam-pyt:请注意,伪代码不是我的,这些是 Python 文档中给出的实际解释。
【解决方案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]

【讨论】:

  • 好吧,这就是我的怀疑。很高兴知道,谢谢你的回答。
猜你喜欢
  • 2014-06-25
  • 2016-08-14
  • 2014-04-02
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-25
  • 1970-01-01
相关资源
最近更新 更多