Chained conditional expressions in pythonPosted on July, 12 2015
NOTE: Before you start reading this, read up on stack machines if you have not done so yet. In this post I'll be going over some python bytecode, knowing what a stack machine will help a lot!
False == False in [False]
A friend had recently showed this statement to me, and asked what it evaluates
to. I replied 'False', of course, since
False == False is
is not in the list
[False]. Until, of course, I threw it in the python
>>> False == False in [False] True
>>> def func(): ... # We'll name these constants as they pop up in the stack ... # A B C ... return False == False in [False] ... >>> import dis >>> dis.dis(func) 2 0 LOAD_GLOBAL 0 (False) 3 LOAD_GLOBAL 0 (False) 6 DUP_TOP 7 ROT_THREE 8 COMPARE_OP 2 (==) 11 JUMP_IF_FALSE_OR_POP 24 14 LOAD_GLOBAL 0 (False) 17 BUILD_LIST 1 20 COMPARE_OP 6 (in) 23 RETURN_VALUE >> 24 ROT_TWO 25 POP_TOP 26 RETURN_VALUE
On lines 0-3,
LOAD_GLOBAL is called twice to load
B into the stack. Below is a representation of the stack, with the label/ID mnemonic above each stack element.
# TOS (top-of-stack), TOS1 # B A stack = [False, False]
DUP_TOP duplicates the 'top' of the stack.
# TOS , TOS1 # DUP.B B A stack = [False, False, False]
ROT_THREE rotates the top three elements in the stack.
# TOS , TOS1 # B A DUP.B stack = [False, False, False]
The next instruction is
COMPARE_OP(==), which compares if
TOS == TOS1,
TOS1 before pushing the result of
TOS == TOS1.
# TOS , TOS1 # A==B, DUP.B stack = [True, False]
JUMP_IF_FALSE_OR_POP jumps if
TOS == False, else it pops the stack and moves
# TOS # DUP.B stack = [False]
Huh, won't you look at that? The result of
False == False was thrown away
completely, leaving only the duplicate of B in the stack. This tells me that my
initial theory was completely wrong, python is not chaining these operations
together at all!
Now, the next set of instructions (14-17) builds the list, by pushing
the stack and using
BUILD_LIST(size) to create the list.
# TOS , TOS1 # C B stack = [List, False]
COMPARE_OP(in) tests whether
TOS1 is in list
TOS, which is
# TOS stack = [True]
The line after that is
RETURN_VALUE, which returns the value of
is what we get when we run func()
From what we can see, python does not chain these operations together as expected. It actually doesn't chain the operations together at all.
Looking at what values are in the stack at each instruction, we can see that
instead of keeping the result of
A == B, it keeps what we named as
in the stack and tested whether or not
B was in the list
C. On line 11,
JUMP_IF_FALSE_OR_POP was used. If
A == B were to evaluate to
, it would end up jumping to line
24 and return
In short, what the bytecode tells us is that the statement we wrote as
A == B in [C] is interpreted as
A == B and B in C.
This must mean that given a statement structure as
A is B is C, the statement
is expanded into
A is B and B is C.