为什么在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为元组:
-
(0, 0) == 0
(这是False
),接着是0
-
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
,简化为TUPLE
。 TUPLE EQ ZERO
对TUPLE 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正在制作一个元组:
- expression式
(0, 0) == 0
,计算结果为False
- 常数
0
第二个是相反的。