5

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.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
stam
  • 109
  • 2
  • 6
  • 1
    I decided that with an appropriate title, this is not really a duplicate. However, see also: [How do Python's any and all functions work?](https://stackoverflow.com/questions/19389490/) and [Lazy Function Evaluation in any() / all()](https://stackoverflow.com/questions/64090762/). – Karl Knechtel May 11 '22 at 20:09
  • See also https://stackoverflow.com/questions/17246388 – Karl Knechtel Aug 03 '22 at 00:53

3 Answers3

8

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)
))
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • *"A function's arguments have to be evaluated before the function is called"* - You'd [thunk](https://en.wikipedia.org/wiki/Thunk) so. Just kidding, although I do only know that word [thanks to Python](https://github.com/python/cpython/blob/079f0dd7191fbadd4c3a5899b6af12492e84d2b4/Lib/linecache.py#L16). – Kelly Bundy May 12 '22 at 16:47
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

Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
  • 1
    Yes, in trivial cases it certainly makes more sense to do this. `Simple is better than complex`. However, this doesn't really extend to the case where the sequence of conditions is customizable at runtime. – Karl Knechtel May 11 '22 at 20:15
0

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()))
Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65