Comparison chaining. 2 == 2 == True
is evaluated as 2 == 2 and 2 == True
, and the second comparison is clearly false. (2
may be truthy, but it is not True
.)
You can use the ast
module to see the different abstract syntax trees (ASTs) produced from each expression.
>>> print(ast.dump(ast.parse("2 == 2 == True").body[0], indent=2))
Expr(
value=Compare(
left=Constant(value=2),
ops=[
Eq(),
Eq()],
comparators=[
Constant(value=2),
Constant(value=True)]))
>>> print(ast.dump(ast.parse("(2 == 2) == True").body[0], indent=2))
Expr(
value=Compare(
left=Compare(
left=Constant(value=2),
ops=[
Eq()],
comparators=[
Constant(value=2)]),
ops=[
Eq()],
comparators=[
Constant(value=True)]))
Looking at the AST alone doesn't make it obvious that A op1 B op2 C
is equivalent to A op1 B and B op2 C
(You can see that there is a single Compare
expression with two operators and two "comparators", rather than a Compare
expression with a single operator and another Compare
expression as one operand.) For that, you need the documentation:
Formally, if a
, b
, c
, …, y
, z
are expressions and op1
, op2
, …, opN
are comparison operators, then a op1 b op2 c ... y opN z
is equivalent to a op1 b and b op2 c and ... y opN z
, except that each expression is evaluated at most once.
The AST provides the information, the code generator provides the semantics of that information.
>>> import dis
>>> dis.dis('2==2==True')
1 0 LOAD_CONST 0 (2)
2 LOAD_CONST 0 (2)
4 DUP_TOP
6 ROT_THREE
8 COMPARE_OP 2 (==)
10 JUMP_IF_FALSE_OR_POP 18
12 LOAD_CONST 1 (True)
14 COMPARE_OP 2 (==)
16 RETURN_VALUE
>> 18 ROT_TWO
20 POP_TOP
22 RETURN_VALUE
>>> dis.dis('(2==2)==True')
1 0 LOAD_CONST 0 (2)
2 LOAD_CONST 0 (2)
4 COMPARE_OP 2 (==)
6 LOAD_CONST 1 (True)
8 COMPARE_OP 2 (==)
10 RETURN_VALUE