Any/All python short-circuit: Why doesn't the following work?

Question:

Based on what I’ve seen on other stackoverflow pages:

the following code should short circuit:

any([True, 2+2, False, 2/0])
all([True, 2+2, False, 2/0])

but for every one of them I get a ZeroDivisionError: division by zero.

Am I missing something? Why does it error?

Asked By: hainabaraka

||

Answers:

Yes, short circuting happens in python

In [23]: False and 3/0
Out[23]: False

In [24]: True and 3/0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-24-a08823d6496a> in <module>
----> 1 True and 3/0

ZeroDivisionError: division by zero

Using the dis module,

In [32]: dis.dis("all([True, 2+2, False, 2/0])")
  1           0 LOAD_NAME                0 (all)
              2 LOAD_CONST               0 (True)
              4 LOAD_CONST               1 (4)
              6 LOAD_CONST               2 (False)
              8 LOAD_CONST               3 (2)
             10 LOAD_CONST               4 (0)
             12 BINARY_TRUE_DIVIDE
             14 BUILD_LIST               4
             16 CALL_FUNCTION            1
             18 RETURN_VALUE

In [33]: dis.dis("any(True, 2+2, False, 2/0)")
  1           0 LOAD_NAME                0 (any)
              2 LOAD_CONST               0 (True)
              4 LOAD_CONST               1 (4)
              6 LOAD_CONST               2 (False)
              8 LOAD_CONST               3 (2)
             10 LOAD_CONST               4 (0)
             12 BINARY_TRUE_DIVIDE
             14 CALL_FUNCTION            4
             16 RETURN_VALUE

When you look at the function calls, BINARY_TRUE_DIVIDE is evaluated before any or all because expressions get evaluated first before any function call

Answered By: bigbounty

Your code errors because the expressions have to be evaluated before being passed into the function.

In this context, short-circuiting actually means that as soon as they find a different value, they return and don’t bother checking the remaining values. It’s not the same kind of short-circuiting that and and or do, which can actually avoid evaluating expressions.

To illustrate, let’s use an iterator, which will get consumed:

>>> a = iter([1, 0, 2, 3])
>>> all(a)  # Search until a falsy value
False
>>> list(a)  # Show what's left in the iterator
[2, 3]

You can see that it consumed 1 and 0, returning on 0 because it’s falsy.


Now, if you did want to do lazy evaluation with any and all, you could call lambdas in a generator expression:

>>> any(e() for e in [lambda: 1, lambda: 0, lambda: 2/0])
True
>>> all(e() for e in [lambda: 1, lambda: 0, lambda: 2/0])
False

(Thanks to this question for inspiring this bit.)

Answered By: wjandrea
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.