What does it mean that Python comparison operators chain/group left to right?
Grouping (this is what non-comparison operators do):
a + b + c means (a + b) + c
Chaining (this is what comparison operators do):
a < b < c means (a < b) and (b < c)
Grouping left to right (this is the way things are grouped):
5 - 2 - 1 means (5 - 2) - 1 == 2
as opposed to grouping right to left (this would produce a different result):
5 - (2 - 1) == 4
Chaining left to right
Chaining is left to right, so in a < b < c
, the expression a < b
is evaluated before b < c
, and if a < b
is falsey, b < c
is not evaluated.
(2 < 1 < f())
gives the value False
without calling the function f
, because 2 < 1
evaluates to false, so the second comparison does not need to be performed.
f() > 1 > g()
calls f()
in order to evaluate the first comparison, and depending on the result, it might or might not need to evaluate the second condition, which requires calling g()
.
NB. Each operand is evaluated at most once. So in the expression 1 < f() < 2
, the function f()
is only called once, and the value it gives is used in both comparisons (if necessary).
https://en.wikipedia.org/wiki/Short-circuit_evaluation
Precedence of chained comparisons?
Python has an Abstract Syntax Tree module to show you what's happening:
import ast
t = ast.parse('not a < b < c')
print(ast.dump(t))
It gives (cleaned up a bit):
[Expr(value=UnaryOp(
op=Not(),
operand=Compare(
left=Name(id='a'),
ops=[Lt(), Lt()],
comparators=[Name(id='b'), Name(id='c')]
)
))]
And indeed, the documentation says that not
has lower precedence than <
.
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
Why 5 = -15 == 5 = 1 != 20 is False?
Python's operator precedence is documented here. Contrary to what you claim, all of those comparison operators have the same precedence.
Normally, one looks to associativity to handle ties in precedence. But the comparison operators are special. Python supports chaining. This means that
x relop1 y relop2 z
is short for
x relop1 y and y relop2 z
(Except y
is only evaluated once.)
So,
5 <= -15 == 5 >= 1 != 20
= ( 5 <= -15 ) and ( -15 == 5 ) and ( 5 >= 1 ) and ( 1 != 20 )
= False and False and True and True
= False
How a single parenthesis could change the output?
Because of Python's comparison chaining feature.
3 in [1, 2, 3] == [1, 2, 3]
is treated as
(3 in [1, 2, 3]) and ([1, 2, 3] == [1, 2, 3])
Comparison operators and 'is' - operator precedence in python?
You're seeing python's operator chaining working
5 > 2 is True
Is equivalent to
5>2 and 2 is True
You can see this in that
>>> 5>2 is 2
Returns True
.
In which order are two or more consecutive membership operators treated in Python?
This is operator chaining and will expand to 1 in g and g in g
. So only after you append g
to itself this becomes true.
You can use parentheses to get the behavior you want: (1 in g) in g
. This forces the 1 in g
part to be evaluated first (however in
compares for equality and True == 1
so True
doesn't need to be part of the list actually).
Related Topics
Python Enum, When and Where to Use
Escape String Python for MySQL
Pandas Split Column into Multiple Columns by Comma
Ssl.Sslerror: Tlsv1 Alert Protocol Version
How to Calculate Mean Values Grouped on Another Column in Pandas
Want to Find Contours -> Valueerror: Not Enough Values to Unpack (Expected 3, Got 2), This Appears
Is It Safe to Replace a Self Object by Another Object of the Same Type in a Method
Adding a Background Image to a Plot
Replicating Rows in a Pandas Data Frame by a Column Value
Attributeerror: 'Tensor' Object Has No Attribute 'Numpy'
How to Sort a List with Two Keys But One in Reverse Order
Converting String to Int Using Try/Except in Python
Oserror [Errno 22] Invalid Argument When Use Open() in Python