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

7

122

In Python (I checked only with Python 3.6 but I believe it should hold for many of the previous versions as well):

(0, 0) == 0, 0   # results in a two element tuple: (False, 0)
0, 0 == (0, 0)   # results in a two element tuple: (0, False)
(0, 0) == (0, 0) # results in a boolean True

But:

a = 0, 0
b = (0, 0)
a == b # results in a boolean True

Why does the result differ between the two approaches? Does the equality operator handle tuples differently?

Suannesuarez answered 1/7, 2017 at 18:29 Comment(0)
B
158

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.

Brickey answered 1/7, 2017 at 18:31 Comment(4)
I'd say that the comma operator has lower precedence than equality, since evaluation of equality precedes that of the comma operator: equality has higher precedence than the comma operator. But this is always a source of confusion; just wanted to point out that other sources might flip things around.Tradescantia
You can avoid the lower/higher verbiage confusion by instead saying that , binds less tightly than ==.Symptom
The comma is not an operator docs.python.org/3.4/faq/…Consueloconsuetude
The docs can claim that all they want, but it doesn't matter. You can write a parser so that every operator gets its own production and there's no explicit "precedence" anywhere in the implementation, but that doesn't keep those syntactic units from being operators.You can redefine "operator" in some implementation-specific way, which is apparently what they did In Python, but that doesn't change the implication of the term. The comma is effectively an operator which produces tuples. Its operatorness shows in, for example, the way its relative precedence is affected by parentheses.Brickey
S
69

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.

Stern answered 1/7, 2017 at 18:42 Comment(0)
S
21

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
Snipe answered 2/7, 2017 at 21:23 Comment(0)
D
17

Adding a couple of parentheses around the order in which actions are performed might help you understand the results better:

# Build two element tuple comprising of 
# (0, 0) == 0 result and 0
>>> ((0, 0) == 0), 0
(False, 0)

# Build two element tuple comprising of
# 0 and result of (0, 0) == 0 
>>> 0, (0 == (0, 0))
(0, False)

# Create two tuples with elements (0, 0) 
# and compare them
>>> (0, 0) == (0, 0) 
True

The comma is used to to separate expressions (using parentheses we can force different behavior, of course). When viewing the snippets you listed, the comma , will separate it and define what expressions will get evaluated:

(0, 0) == 0 ,   0
#-----------|------
  expr 1      expr2

The tuple (0, 0) can also be broken down in a similar way. The comma separates two expressions comprising of the literals 0.

Drake answered 1/7, 2017 at 21:30 Comment(0)
T
6

In the first one Python is making a tuple of two things:

  1. The expression (0, 0) == 0, which evaluates to False
  2. The constant 0

In the second one it's the other way around.

Tardif answered 1/7, 2017 at 18:32 Comment(0)
E
0

look at this example:

r = [1,0,1,0,1,1,0,0,0,1]
print(r==0,0,r,1,0)
print(r==r,0,1,0,1,0)

then result:

False 0 [1, 0, 1, 0, 1, 1, 0, 0, 0, 1] 1 0
True 0 1 0 1 0

then comparison just does to the first number(0 and r) in the example.

Evy answered 2/10, 2018 at 16:47 Comment(0)
C
0

I had a similar question. I am not a computer scientist, I am either a software engineer or a computer programmer. So I asked the python interpreter and this is what I found, empirically.

>>> t1 = ()
>>> "True" if t1 else "False"
'False'
>>> t1 = (False)     # That's because t1 is not a tuple!
>>> "True" if t1 else "False"
'False'
>>> t1 = (False,)     # t1 is a tuple.  So , is an operator as mentioned above
>>> "True" if t1 else "False"
'True'
>>> t1 = (False, 1)
>>> "True" if t1 else "False"
'True'
>>> t1 = (False, False)
>>> "True" if t1 else "False"
'True'
>>> type(False,)
<class 'bool'>
>>> type((False,))
<class 'tuple'>
>>> type(False)
<class 'bool'>
>>> type((False))
<class 'bool'>
>>>

I did a lot of testing and the only tuple I found that evaluate to False is the empty tuple.

I also learned something in this exercise. A lot of rookies use the idiom:

if BOOLEAN_EXPRESSION == False:

instead of

if not BOOLEAN_EXPRESSION:

"Why is this a bad thing to do?", they ask me. Now, I have a good answer:

>>> (False,) == False
False
>>> t1=(False,)
>>> "True" if t1 else "False"
'True'
>>> t1 == False
False
>>>
>>> t1=(False,)
>>> "True" if t1 else "False"
'True'
>>> t1 == False
False
>>> t1 is False
False
>>> not t1 is False
True
>>> not ( t1 is False )
True
>>>
>>> "True" if t1 else "False"
'True'
>>> "True" if not t1 else "False"
'False'
>>> "True" if t1 == True else "False"
'False'
>>>


So even though (False,) evaluates to False, it is not False.

I would like to thank you for asking this question to my attention. It's a great question.

Cassel answered 31/8, 2020 at 23:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.