'Why do `any` and `all` not appear to use short-circuit evaluation here?

Does all() return False right after finding a False in a sequence?
Try to run this code:

def return_true():
    print('I have just been printed')
    return True

print(all((False, return_true())))

As you can see, I have just been printed is printed even though there is False before it.

Another example:

def return_false():
    print('I have just been printed')
    return False

print(any((True, return_false())))

In this case, I have just been printed is printed in this code even though there is True before.



Solution 1:[1]

Yes, all() and any() both short circuit the way you describe. all() will return early if any item is false-y, and any() will if any item is truthy.

The reason you're seeing the printouts is because return_true() and return_false() are being called before all and any are even invoked. They must be. A function's arguments have to be evaluated before the function is called, after all.

This:

print(all((False, return_true())))

is equivalent to:

x = return_true()
print(all((False, x)))

i.e. return_true() is evaluated unconditionally.

To get the desired short-circuiting behavior, the sequence itself needs to be evaluated lazily. One simple way to do this is to make an iterable, not of the values we want to test, but of things we can call to get those values; and then use a generator expression to create a sequence that lazily calls them. See also.

Here, that might look like:

print(all(
    x()
    for x in (lambda: False, return_true)
))

print(any(
    x()
    for x in (lambda: True, return_false)
))

Solution 2:[2]

What you may be looking for is just a plain old if with conditions connected by and:

def return_true():
    print('I have just been printed')
    return True

if False and return_true():
    print('inside if')

This prints nothing! A true short-circuit operator with no extra evaluations.


SEE ALSO:

all source code: search for builtin_all here: https://github.com/python/cpython/blob/main/Python/bltinmodule.c

Solution 3:[3]

You already compute all values before all is even called. Here's another way to avoid that, a generator:

def return_true():
    print('I have just been printed')
    return True

def values():
    yield False
    yield return_true()

print(all(values()))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Karl Knechtel
Solution 2 Timur Shtatland
Solution 3