Python的`all`函数是否使用短路求值?

12
我希望使用Python的all()函数来帮助我计算一些东西,但是如果all()在遇到False时不立即评估,这个计算可能会花费更长的时间。我想它可能是短路评估的,但我只是想确认一下。另外,在Python中有没有办法知道函数是如何评估的?
编辑注:由于anyall函数,在调用它们之前必须先评估它们的参数。这经常会给人一种没有短路的印象,但它们仍然会进行短路操作。为了解决这个问题,可以传递一个生成器表达式或其他惰性评估的表达式,而不是一个序列。有关详细信息,请参阅any() / all()中的惰性函数评估

1
我反转了重复关闭的方向,因为这个版本的问题更容易理解。之前的重复问题 https://dev59.com/c2Uq5IYBdhLWcg3wKtXs 直接进入Python测试代码,并最终成为针对Python测试套件的错误报告的基础。 - Karl Knechtel
4个回答

21
是的,它发生了短路。
>>> def test():
...     yield True
...     print('one')
...     yield False
...     print('two')
...     yield True
...     print('three')
...
>>> all(test())
one
False

docs中:

Return True if all elements of the iterable are true (or if the iterable is empty). Equivalent to:

def all(iterable):
    for element in iterable:
        if not element:
            return False
    return True
所以当它返回False时,函数立即中断。

"yield" 是什么作用? - Sylvester V Lowell
@SylvesterVLowell 请查看Python yield关键字解释 - user395760
@SylvesterVLowell 请看这里。简单来说,它就像一个return语句,但是你可以返回多个值。但是它不是返回一个列表,而是返回一个生成器。 - TerryA
简而言之,yield 是生成器的工作原理。如果你知道 all 可能会短路,我想你知道生成器 做什么,只是不知道它们如何工作。 - morningstar
确保短路的部分在这里:http://hg.python.org/cpython/rev/124237eb5de9 - jamylak

7

是的,all确实使用短路求值。例如:

all(1.0/x < 0.5  for x in [4, 8, 1, 0])
=> False

上述代码在列表中当x达到1时停止,当条件变为false。如果all没有短路,当x达到0时,我们将得到除以零的结果。

3

请确保不要像我最初那样尝试使用短路来测试调用方法之前是否存在:

>>> class MyClass(object):
...    pass
...
>>> a=MyClass()
>>> all([hasattr(a,'b'), a.b()])

Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'b'

但是
>>> a=MyClass()                   
>>> hasattr(a,'b') and a.b()   #doesn't evaluate a.b() as hasattr(a,'b') is false

False

在第一个代码片段中,Python在将列表传递给all()之前对其进行了评估,因此仍会引发异常。这基本上与使用list()强制all()不使用短路评估的方法相同,就像morningstar的答案一样。

2
反思后发现这种方法不太可行,因为Python在将列表传递给all()函数之前会先进行评估,所以抛出异常的是[hasattr(a,'b'), a.b()],而不是all()语句本身。如下all([hasattr(a,'b') and a.b(),hasattr(c,'d') and c.d()])仍然有效。 - Danny
Python必须具有非常懒惰的求值才能做到您最初期望的那样。 - Blacklight Shining
你应该将那个评论编辑到你的回答中。 - Blacklight Shining
我尝试从头开始编写一个答案,更清晰地解释这种行为。 - Karl Knechtel

-1
回答你的问题,关于是否可以让all进行短路求值或者不进行短路求值,它默认是进行短路求值的。但是如果你想让它不进行短路求值,你可以这样做:
result = all(list(iterable))

尽管这可能具有不良的属性,即整个列表将被加载到内存中。我无法想象除了使用不同于“all”函数之外如何避免这种情况。例如:

result = reduce(lambda x,y: x and y, iterable)
result = min(iterable) # surprisingly similar to all; YMMV if iterable contains non-booleans

不要使用min()替换all()(或max()替换any())。 all()会在遇到falsey对象时立即返回Falsemin()将始终搜索整个可迭代对象以查看是否有“较小”的对象。any()max()的工作方式类似。此外,它们四个都适用于任何可迭代对象-不要浪费时间和空间制作一个list - Blacklight Shining
它会热切地评估可迭代对象,但实际的布尔检查逻辑仍然会短路。不过通常情况下这并不重要。 - Karl Knechtel
"如果你不希望它成为现实" - 为什么你会有这样的想法呢?我脑海中暂时想不出任何情况。 - undefined
@wjandrea,我想不出一个具体的例子,但如果有人想要在某个可迭代对象的每个项目上调用一个带有期望副作用的函数,并返回一个布尔值,并且还要在这些布尔值上计算all(),我可以想象到一些人不熟悉短路操作会在all的调用中使用生成器表达式,并且不理解为什么函数没有在可迭代对象的每个项目上被调用。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接