Why in Python does "0, 0 == (0, 0)" equal "(0, False)"?

Another way to explain the problem: You're probably familiar with dictionary literals

{ "a": 1, "b": 2, "c": 3 }

and array literals

[ "a", "b", "c" ]

and tuple literals

( 1, 2, 3 )

but what you don't realize is that, unlike dictionary and array literals, the parentheses you usually see around a tuple literal are not part of the literal syntax. The literal syntax for tuples is just a sequence of expressions separated by commas:

1, 2, 3

(an "exprlist" in the language of the formal grammar for Python).

Now, what do you expect the array literal

[ 0, 0 == (0, 0) ]

to evaluate to? That probably looks a lot more like it should be the same as

[ 0, (0 == (0, 0)) ]

which of course evaluates to [0, False]. Similarly, with an explicitly parenthesized tuple literal

( 0, 0 == (0, 0) )

it's not surprising to get (0, False). But the parentheses are optional;

0, 0 == (0, 0)

is the same thing. And that's why you get (0, False).


If you're wondering why the parentheses around a tuple literal are optional, it is largely because it would be annoying to have to write destructuring assignments that way:

(a, b) = (c, d) # meh
a, b = c, d     # better

The first two expressions both parse as tuples:

  1. (0, 0) == 0 (which is False), followed by 0
  2. 0, followed by 0 == (0, 0) (which is still False that way around).

The expressions are split that way because of the relative precedence of the comma separator compared to the equality operator: Python sees a tuple containing two expressions, one of which happens to be an equality test, instead of an equality test between two tuples.

But in your second set of statements, a = 0, 0 cannot be a tuple. A tuple is a collection of values, and unlike an equality test, assignment has no value in Python. An assignment is not an expression, but a statement; it does not have a value that can be included into a tuple or any other surrounding expression. If you tried something like (a = 0), 0 in order to force interpretation as a tuple, you would get a syntax error. That leaves the assignment of a tuple to a variable – which could be made more explicit by writing it a = (0, 0) – as the only valid interpretation of a = 0, 0.

So even without the parentheses on the assignment to a, both it and b get assigned the value (0,0), so a == b is therefore True.


What you see in all 3 instances is a consequence of the grammar specification of the language, and how tokens encountered in the source code are parsed to generate the parse tree.

Taking a look at this low level code should help you understand what happens under the hood. We can take these python statements, convert them into byte code and then decompile them using the dis module:

Case 1: (0, 0) == 0, 0

>>> dis.dis(compile("(0, 0) == 0, 0", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               0 (0)
              6 COMPARE_OP               2 (==)
              9 LOAD_CONST               0 (0)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

(0, 0) is first compared to 0 first and evaluated to False. A tuple is then constructed with this result and last 0, so you get (False, 0).

Case 2: 0, 0 == (0, 0)

>>> dis.dis(compile("0, 0 == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST               0 (0)
              6 LOAD_CONST               2 ((0, 0))
              9 COMPARE_OP               2 (==)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

A tuple is constructed with 0 as the first element. For the second element, the same check is done as in the first case and evaluated to False, so you get (0, False).

Case 3: (0, 0) == (0, 0)

>>> dis.dis(compile("(0, 0) == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               3 ((0, 0))
              6 COMPARE_OP               2 (==)
              9 POP_TOP
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE

Here, as you see, you're just comparing those two (0, 0) tuples and returning True.