为什么在python 0,0 ==(0,0)等于(0,False)

在Python中(我仅用Python 3.6进行了检查,但我相信它也适用于以前的许多版本):

(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 

但:

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

为什么两种方法的结果不一样? 等号运算符是否以不同的方式处理元组?

前两个expression式都parsing为元组:

  1. (0, 0) == 0 (这是False ),接着是0
  2. 0 ,然后是0 == (0, 0) (这仍然是False )。

由于逗号分隔符相对于等号运算符的优先级,expression式被分割:Python看到一个包含两个expression式的元组,其中一个恰好是一个等式testing,而不是两个元组之间的相等性testing。

但是在你的第三个例子中, a = 0, 0 不能是一个元组。 元组是一个值的集合,与平等testing不同,赋值在Python中没有任何价值。 一项任务不是一个expression,而是一个陈述; 它没有可以包含在元组或任何其他周围expression式中的值。 如果你尝试了像(a = 0), 0这样的强制解释作为元组,你会得到一个语法错误。 这就把一个元组的赋值赋给了一个variables – 通过将它写成a = (0, 0)这个variables可以变得更加明确 – 是对a = 0, 0唯一有效的解释。

您在所有3个实例中看到的是语言的语法规范的结果,以及源代码中遇到的令牌如何parsing以生成分析树。

看看这个低级别的代码应该可以帮助你理解底下发生了什么。 我们可以把这些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 

一个元组被构造为0作为第一个元素。 对于第二个元素,完成与第一个例子相同的检查,并将其评估为False ,以便得到(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


究竟发生了什么?

Python使用LALRparsing器,从左到右parsing令牌。

这是典型的parsing语句的方式(注意我使用的语法规则是指示性的)。 实际的规则遵循类似的模式。

  (0, 0) == 0, 0 | <empty stack> ^ start of expression (0, 0) == 0, 0 | LPAREN ^ 0 found (0, 0) == 0, 0 | LPAREN ZERO ^ comma found (0, 0) == 0, 0 | LPAREN ZERO COMMA ^ 0 found (0, 0) == 0, 0 | LPAREN ZERO COMMA ZERO ^ closing expression (0, 0) == 0, 0 | TUPLE ^ comparison operator (0, 0) == 0, 0 | TUPLE EQ ^ 0 found (0, 0) == 0, 0 | TUPLE EQ ZERO ^ comma found (0, 0) == 0, 0 | TUPLE EQ ZERO COMMA ^ 0 found 

最后的expression是TUPLE EQ ZERO COMMA ZERO ,简化为TUPLETUPLE EQ ZEROTUPLE EQ ZERO进行评估,然后将结果与0结合形成最终的元组,给出(False, 0)

解决问题的另一种方法:您可能熟悉字典文字

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

和数组文字

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

和元组文字

 ( 1, 2, 3 ) 

但是你没有意识到的是,与字典和数组文字不同的是,你通常在元组文字周围看到的圆括号不是字面语法的一部分 。 元组的文字语法只是用逗号分隔的一系列expression式:

 1, 2, 3 

( Python语言中的“exprlist”)。

现在,你期望数组文字

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

评估? 这可能看起来更像是应该是一样的

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

这当然评估为[0, False] 。 同样,用明确的括号化的元组文字

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

得到(0, False)并不奇怪。 但括号是可选的;

 0, 0 == (0, 0) 

是一回事 这就是为什么你得到(0, False)


如果你想知道为什么元组文字上的圆括号是可选的,那很大程度上是因为不得不这样写解构赋值:

 (a, b) = (c, d) # meh a, b = c, d # 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 

逗号用于分隔expression式 (当然,使用括号可以强制不同的行为)。 查看您列出的片段时,逗号将分隔它,并定义要评估的expression式:

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

元组(0, 0)也可以用类似的方式分解。 逗号分隔包含文字0两个expression式。

在第一个中,Python正在制作一个元组:

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

第二个是相反的。