Any/All python short-circuit: Why doesn't the following work?
Question:
Based on what I’ve seen on other stackoverflow pages:
- Does Python's `all` function use short circuit evaluation?
- Do all() and any() always short-circuit in order?
- Is the shortcircuit behaviour of Python's any/all explicit?
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?
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
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.)
Based on what I’ve seen on other stackoverflow pages:
- Does Python's `all` function use short circuit evaluation?
- Do all() and any() always short-circuit in order?
- Is the shortcircuit behaviour of Python's any/all explicit?
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?
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
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.)