How do chained comparisons in Python actually work?
You can simply let Python tell you what bytecode is produced with the dis
module:
>>> import dis
>>> def f(): return 1 < input("Value:") < 10
...
>>> dis.dis(f)
1 0 LOAD_CONST 1 (1)
3 LOAD_GLOBAL 0 (input)
6 LOAD_CONST 2 ('Value:')
9 CALL_FUNCTION 1
12 DUP_TOP
13 ROT_THREE
14 COMPARE_OP 0 (<)
17 JUMP_IF_FALSE_OR_POP 27
20 LOAD_CONST 3 (10)
23 COMPARE_OP 0 (<)
26 RETURN_VALUE
>> 27 ROT_TWO
28 POP_TOP
29 RETURN_VALUE
Python uses a stack; the CALL_FUNCTION
bytecode uses items on the stack (the input
global and the 'Value:'
string) to call a function with one argument, to replace those two items on the stack with the result of the function call. Before the function call, the the constant 1
was loaded on the stack.
So by the time input
was called the stack looks like:
input_result
1
and DUP_TOP
duplicates the top value, before rotating the top three stack values to arrive at:
1
input_result
input_result
and a COMPARE_OP
to test the top two items with <
, replacing the top two items with the result.
If that result was False
the JUMP_IF_FALSE_OR_POP
bytecode jumps over to 27, which rotates the False
on top with the remaining input_result
to clear that out with a POP_TOP
, to then return the remaining False
top value as the result.
If the result True
however, that value is popped of the stack by the JUMP_IF_FALSE_OR_POP
bytecode and in it's place the 10
value is loaded on top and we get:
10
input_result
and another comparison is made and returned instead.
In summary, essentially Python then does this:
stack_1 = stack_2 = input('Value:')
if 1 < stack_1:
result = False
else:
result = stack_2 < 10
with the stack_*
values cleared again.
The stack, then, holds the unnamed intermediate result to compare