10

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)))
Ma0
  • 15,057
  • 4
  • 35
  • 65

7 Answers7

20

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
Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
  • 1
    i usually find the docs bad but in this case it is beyond clear. Thanks a lot! Also this is exactly what i had in mind in terms of _lazy evaluation_. how is _short-circuiting_ any different ? – Ma0 Sep 06 '16 at 12:09
  • 1
    The generator expression is lazy in the sense that it only calls `some(x)` when the next item is requested. `any/all` short-circuit in the sense that it will only request items until the outcome is clear. You could say that `any([some(x) for x in range(5)])` still short-circuits, but isn't lazy since the list comprehension results in `some` being called on all elements of `range(x)` before `any` begins, but `any` will still stop examining the input list as soon as possible. – chepner Sep 06 '16 at 12:18
16

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.

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
  • I find this confusing because I know that `range()` creates the full list in python2, but then you wrap it in a generator, which is lazy – Chris_Rands Sep 06 '16 at 12:28
  • @Chris_Rands: When you use a *generator expression*, like in the example above, the `for`-expression is immediately evaluated. Hence, `range()` constructs a full list even before the generator is run. – Eugene Yarmash Sep 06 '16 at 14:09
9

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.

Community
  • 1
  • 1
2

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.

mhawke
  • 84,695
  • 9
  • 117
  • 138
2

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
John Coleman
  • 51,337
  • 7
  • 54
  • 119
1

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

JoshiRaez
  • 97
  • 9
  • 1
    this is actually the only correct answer. Here is an example ``` a = [] any([True, a[0]]) ``` Will fail Alternatively, using OR (or and) will not fail since its not a function: ``` a = [] True or a[0] ``` – Manuel G Dec 28 '20 at 12:24
-1

JoshiRaez is the actual correct answer.

Here is an example

a = []

any([True, a[0]])

will fail

On the other side, using OR (or AND) will not fail since its not a function:

a = []
True or a[0]
Manuel G
  • 1,523
  • 1
  • 21
  • 34