Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is any() evaluated lazily?

Tags:

python

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)))
like image 845
Ma0 Avatar asked Sep 06 '16 12:09

Ma0


6 Answers

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
like image 170
Tim Pietzcker Avatar answered Nov 15 '22 05:11

Tim Pietzcker


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.

like image 21
Eugene Yarmash Avatar answered Nov 15 '22 06:11

Eugene Yarmash


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.

like image 36
Kroltan Avatar answered Nov 15 '22 05:11

Kroltan


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.

like image 36
mhawke Avatar answered Nov 15 '22 07:11

mhawke


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
like image 2
John Coleman Avatar answered Nov 15 '22 07:11

John Coleman


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

like image 1
JoshiRaez Avatar answered Nov 15 '22 06:11

JoshiRaez