Python中的any/all函数是否明确支持短路行为?

57

这里的讨论启发

文档提供了一些等效代码,用于allany的行为。

等效代码的行为是否应被视为定义的一部分,或者实现是否可以以非短路方式实现它们?

以下是cpython/Lib/test/test_builtin.py中的相关摘录

def test_all(self):
    self.assertEqual(all([2, 4, 6]), True)
    self.assertEqual(all([2, None, 6]), False)
    self.assertRaises(RuntimeError, all, [2, TestFailingBool(), 6])
    self.assertRaises(RuntimeError, all, TestFailingIter())
    self.assertRaises(TypeError, all, 10)               # Non-iterable
    self.assertRaises(TypeError, all)                   # No args
    self.assertRaises(TypeError, all, [2, 4, 6], [])    # Too many args
    self.assertEqual(all([]), True)                     # Empty iterator
    S = [50, 60]
    self.assertEqual(all(x > 42 for x in S), True)
    S = [50, 40, 60]
    self.assertEqual(all(x > 42 for x in S), False)

def test_any(self):
    self.assertEqual(any([None, None, None]), False)
    self.assertEqual(any([None, 4, None]), True)
    self.assertRaises(RuntimeError, any, [None, TestFailingBool(), 6])
    self.assertRaises(RuntimeError, all, TestFailingIter())
    self.assertRaises(TypeError, any, 10)               # Non-iterable
    self.assertRaises(TypeError, any)                   # No args
    self.assertRaises(TypeError, any, [2, 4, 6], [])    # Too many args
    self.assertEqual(any([]), False)                    # Empty iterator
    S = [40, 60, 30]
    self.assertEqual(any(x > 42 for x in S), True)
    S = [10, 20, 30]
    self.assertEqual(any(x > 42 for x in S), False)

它不会执行任何操作来强制短路行为。

有趣的是测试套件没有强制短路。在我看来,这似乎是一个疏忽。尽管如此,我仍然认为短路是规范的一部分。 - mgilson
5
我在你发布的代码中发现了一个错误,并提交了一个问题报告 an issue - Sjoerd
6
我也提交了一个问题(http://bugs.python.org/issue17255),针对所讨论的行为。 - wim
1
@wim,很高兴最终能够解决这个问题 :) - John La Rooy
4个回答

76

行为是有保障的。我贡献了一个补丁,该补丁已被接受并合并最近,因此如果您获取最新的源代码,您将看到短路行为现在被明确执行。

git clone https://github.com/python/cpython.git
grep Short-circuit cpython/Lib/test/test_builtin.py

34
看到这个问题的时间轴真是太棒了:2月13日提出问题,2月14日回答有第一次修改,2月20日错误/补丁被创建,2月21日合并,2月23日更新了此回答。感谢这个问题在Stack Overflow上被提出,Python库变得更好了。(当然还要感谢@wim) - ShreevatsaR

17

文档说:

“如果可迭代对象中有任意一个元素为 true,则返回 True。如果可迭代对象为空,则返回 False。等同于:”(强调是我的)...

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

如果 any 没有短路,它就不会与发布的代码等效,因为发布的代码显然进行短路操作。例如,您可能会消耗比想要的生成器更多的内容。基于此,我断言 短路行为是有保障的

对于 all,同样可以提出完全相同的论点。


2
@gnibbler -- 我认为不会有任何反驳意见(除了可能是Simon在你的原始帖子上)。 就我而言,这是明确记录的行为... - mgilson
@gnibbler -- 如果你正在寻找有趣的关于实现方面的讨论,请查看我、MartijnPieters和DSM之间关于execlocals的评论,从这里开始,一直到这里结束。 - mgilson
1
我认为没有任何反驳的余地;根据规范,anyall都会短路运算。对于使用它进行迭代器推进,可以提出纯粹的风格问题,但它总是会做正确的事情。 - DSM

13

请注意,这并不回答OP的完全不同的问题。

如果您到这里想知道为什么any/all调用似乎不会短路,请注意一个错误的期望:在调用中使用列表推导式并期望列表的构建短路:

>>> def print_and_return_num(num):
    print_and_return_num(num)
    return num
...

>>> any(print_and_return_num(num) for num in [1, 2, 3, 4])
1
True

>>> any([print_and_return_num(num) for num in [1, 2, 3, 4]])
1
2
3
4
True

在第二个示例中,列表推导式首先被评估:整个列表在any()能够查看它之前被构建。这是预期的行为,但可能需要一秒钟才能看到它等等。


Downvoter:我已经澄清了例子,并且明确了我并没有回答问题。 - scharfmn
2
需要注意的一个好陷阱。在更简单的情况下,any((a, b, c, d))中所有的abcd都会在any开始之前被计算。 - russianfool
值得注意的是:any(hi(), hi(), hi(), hi()) 抛出 TypeError: any() takes exactly one argument (4 given) - scharfmn
1
对的,any() 函数需要一个可迭代对象作为参数。这意味着你可以使用生成器表达式而不添加“额外层次”的[]或()(分别创建列表和元组),但如果它们不是迭代器,则必须包装单个成员。虽然在这里并不是很适用,但要注意成员本身是否是迭代器(例如字符串),因为它会运行但结果错误。 - russianfool
2
这个答案(稍作阐述)最好放在https://dev59.com/Grzpa4cB1Zd3GeqPHkfK。 - Karl Knechtel
非常想要在pylab中使用numpyany方法,但它有不同的行为。 - PascalVKooten

3

由于可能会给出未绑定的可迭代对象,因此必须进行短路处理。如果不进行短路处理,则此过程将永远无法终止:

any(x == 10 for x in itertools.count())

2
我认为这个例子并不能证明它必须短路。你可以很容易地编写像这样永远不会终止的语句,而Python也给了你这样的自由:any( x == -1000 for x in itertools.count() ) - mgilson
1
@mgilson 但是不使用短路运算会使程序(片段)从终止变为不终止。Python也赋予您打印输出的能力,但这并不意味着整数加法可能会打印其结果。 - user395760
3
在这种情况下不终止会很糟糕,使任何东西都变得没有用。我的问题是Python语言是否要求它终止。 - John La Rooy
5
这个问题是关于Python在这种情况下是否“必须”终止的。我认为,提出一个使用any函数不进行短路计算就无法终止的情况并不足以证明该行为是有保障的。你只能从文档中获取这些信息。如果Python的行为与文档不同,则可能是文档或实现的错误(通常是后者)。 - mgilson

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