I am writing a script in which i have to test numbers against a number of conditions. If any of the conditions are met i want to return True
and i want to do that the fastest way possible.
My first idea was to use any()
instead of nested if
statements or multiple or
linking my conditions. Since i would be satisfied if any of the conditions were True
i could really benefit from any()
being lazy and returning True as soon as it could.
Based on the fact that the following print happens instantly and not after 10 (= 0 + 1 + 2 + 3 + 4) seconds i assume it is. Is that the case or am i somehow mistaken?
import time
def some(sec):
time.sleep(sec)
return True
print(any(some(x) for x in range(5)))
Yes, any()
and all()
short-circuit, aborting as soon as the outcome is clear: See the docs:
all(iterable)
Return True if all elements of the iterable are true (or if the iterable is empty). Equivalent to:
def all(iterable): for element in iterable: if not element: return False return True
any(iterable)
Return True if any element of the iterable is true. If the iterable is empty, return False. Equivalent to:
def any(iterable): for element in iterable: if element: return True return False
While the all()
and any()
functions short-circuit on the first "true" element of an iterable, the iterable itself may be constructed in a non-lazy way. Consider this example:
>> any(x == 100 for x in range(10**8))
True
This will take several seconds to execute in Python 2 as range(10**8)
constructs a list of 10**8 elements. The same expression runs instantly in Python 3, where range()
is lazy.
As Tim correctly mentioned, any
and all
do short-circuit, but in your code, what makes it lazy is the use of generators. For example, the following code would not be lazy:
print(any([slow_operation(x) for x in big_list]))
The list would be fully constructed and calculated, and only then passed as an argument to any
.
Generators, on the other hand, are iterables that calculate each item on demand. They can be expressions, functions, or sometimes manually implemented as lazy iterators.
Yes, it's lazy as demonstrated by the following:
def some(x, result=True):
print(x)
return result
>>> print(any(some(x) for x in range(5)))
0
True
>>> print(any(some(x, False) for x in range(5)))
0
1
2
3
4
False
In the first run any()
halted after testing the first item, i.e. it short circuited the evaluation.
In the second run any()
continued testing until the sequence was exhausted.
Yes, and here is an experiment that shows it even more definitively than your timing experiment:
import random
def some(x):
print(x, end = ', ')
return random.random() < 0.25
for i in range(5):
print(any(some(x) for x in range(10)))
typical run:
0, 1, 2, True
0, 1, True
0, True
0, 1, 2, 3, True
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, False
No. All and Any support shortcircuiting but they don't make the conditionals interpretation lazy.
If you want an All or Any using lazy evaluation you need to pass them a generator. Or else the values get evaluated in the moment the list/set/iterator/whatever is constructed
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With