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

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?

10048 次浏览

前两个表达式都解析为元组:

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

由于逗号分隔符与相等运算符的相对优先级,表达式以这种方式进行分割: Python 看到一个包含两个表达式的元组,其中一个恰好是相等测试,而不是两个元组之间的相等测试。

但是在第二组语句中,a = 0, 0 不能是元组。元组是值的集合,与相等性测试不同,赋值在 Python 中没有值。赋值不是表达式,而是语句; 它没有可以包含到元组或任何其他周围表达式中的值。如果您尝试使用类似 (a = 0), 0的东西来强制将解释作为元组,那么您将得到一个语法错误。这样就把元组赋值给一个变量(可以通过写入 a = (0, 0)使其更加明确) ,作为 a = 0, 0的唯一有效解释。

因此,即使没有对 a赋值的括号,它和 b都会被赋值为 (0,0),所以 a == b就是 True

在第一个例子中,Python 做了两个元组:

  1. 计算结果为 False的表达式 (0, 0) == 0
  2. 常数 0

第二种情况正好相反。

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

看一下这个低级代码应该有助于您理解底层发生了什么。我们可以使用这些 python 语句,将它们转换成字节码,然后使用 dis模块反编译它们:

案例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)0进行比较,然后将其评估为 False。然后用这个结果和最后一个 0构造一个元组,这样就得到了 (False, 0)

案例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).

案例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

这里,正如您所看到的,您只是比较这两个 (0, 0)元组并返回 True

在执行操作的顺序前后添加几个括号可能有助于更好地理解结果:

# 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

逗号用于 不同的表达方式(当然,使用括号可以强制执行不同的行为)。当查看您列出的代码片段时,逗号 ,会将其分开,并定义将对哪些表达式求值:

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

元组 (0, 0)也可以以类似的方式进行分解。逗号分隔两个由字面值 0组成的表达式。

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

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

和数组文字

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

and tuple literals

( 1, 2, 3 )

但是您没有意识到的是,与字典和数组文字不同,您通常在元组文字周围看到的括号是 not part of the literal syntax。元组的文字语法只是一个表达式序列,用逗号分隔:

1, 2, 3

(Python 的正式语法语言中的“ exprlist”)。

现在,你期望数组字面值是什么

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

这看起来更像是 应该

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

类似地,使用显式括号的元组文字

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

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

0, 0 == (0, 0)

是一样的,这就是为什么你会得到 (0, False)


如果你想知道 为什么中元组文字周围的括号是可选的,这很大程度上是因为这样写解构作业会很烦人:

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

看看这个例子:

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)

结果是:

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

然后比较就会得到例子中的第一个数字(0和 r)。

我也有同样的问题。我不是一个计算机科学家,我要么是一个软件工程师,要么是一个程序员。所以我问了 Python 解释器,这就是我发现的,经验性的。

>>> 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'>
>>>


我做了很多测试,评估为 False 的 我找到的唯一一个元组是空元组。

我也从这个练习中学到了一些东西,很多新手都用这个成语:

if BOOLEAN_EXPRESSION == False:

而不是

if not BOOLEAN_EXPRESSION:

“为什么这样做不好?”他们问我。现在,我有一个很好的答案:

>>> (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'
>>>




因此,即使(False,)的计算结果为 False,它也是 没有 False。

我想感谢你提出这个问题给我的注意。这是一个伟大的问题。