-3

My problem are these two statements:

> 2 == 2 == True
False
> (2 == 2) == True
True

I am confused. I expected both expressions to be the same since Python would evaluate the expression from left to right, so:

# 2 == 2 == True
# |    |
# ------
#   |
#  True  == True

and

# (2 == 2) == True
#  |    |
#  ------
#    |
#   True   == True

Even if I change the order (if Python evaluated from right to left), I get the same confusing result:

> True == 2 == 2
False
> True == (2 == 2)
True

Can somebody help me here?

mrzo
  • 2,002
  • 1
  • 14
  • 26

1 Answers1

5

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
chepner
  • 497,756
  • 71
  • 530
  • 681