Is the shortcircuit behaviour of Python’s any/all explicit?

Prompted by the discussion here

The docs suggest some equivalent code for the behaviour of all and any

Should the behaviour of the equivalent code be considered part of the definition, or can an implementation implement them in a non-shortcircuit manner?

Here is the relevant excerpt from 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)

It doesn’t do anything to enforce the shortcircuit behaviour

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

The behaviour is guaranteed. I’ve contributed a patch, which was accepted and merged recently, so if you grab the latest sources you will see that the short-circuiting behaviour is now explicitly enforced.

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

Method 2

The docs say

“Return True if any element of the iterable is true. If the iterable is empty, return False. EQUIVALENT TO:” (emphasis mine) …

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

If any didn’t short circuit, it wouldn’t be EQUIVALENT to the posted code since the posted code clearly short circuits. You could consume more of a generator than you want to for example. In light of that, I say that the short circuiting behavior is guaranteed.

The exact same argument could be made for all.

Method 3

In case you landed here looking for “do any/all always always short-circuit?”

They do, but there is a gotcha: using a list comprehension can make it seem like you are overriding the short-circuiting behavior:

def hi():
    print('hi')
    return True

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

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

The list comprehension executes before any() does.

(Note: This does not answer the OP’s very different question. This is the only stackoverflow page that comes up for me when searching “any all short circuit python.”)

Method 4

It HAS to short circuit, since it could be given an unbound iterable. If it did not short circuit then this would never terminate:

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


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x