'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 |