为什么在Python中 "0, 0 == (0, 0)" 等于 "(0, False)"?

122

在 Python 中(我只检查了 Python 3.6,但我相信许多之前的版本也适用):

Translated text:
在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
为什么这两种方法的结果不同?等号操作符是否对元组有不同的处理方式?

为什么这两种方法的结果不同?等号操作符是否对元组有不同的处理方式?

7个回答

158
前两个表达式都可以被解析为元组:
  1. (0,0) == 0 (这是False),接着是 0
  2. 0,接着是 0 == (0,0) (这个方向仍然是 False
这些表达式之所以被分成这样,是因为逗号分隔符相对于等于运算符的优先级较低:Python看到一个包含两个表达式的元组,其中一个恰好是一个相等测试,而不是两个元组之间的相等测试。
但在你的第二组语句中,a=0,0 不能 是一个元组。元组是值的集合,与相等测试不同,Python 中的赋值没有值。赋值不是一个表达式,而是一个语句;它没有一个可以包括在元组或任何其他周围表达式中的值。如果你试图像这样做 (a = 0), 0 来强制将其解释为元组,你会得到一个语法错误。这意味着将元组分配给变量是唯一有效的解释,通过写成 a=(0,0) 可以使其更加明确。
因此,即使在对 a 赋值时没有括号,ab 都被赋予了值 (0,0),因此 a==b 就是 True

17
我认为逗号运算符的优先级低于相等性,因为相等性的评估在逗号运算符之前:相等性的优先级高于逗号运算符。但这总是一个容易混淆的来源;只是想指出其他来源可能会颠倒事情。 - tomsmeding
2
你可以避免使用更低/更高的措辞,而是说“,”比“==”的优先级低。 - amalloy
4
逗号不是运算符。请参考此链接了解更多信息:https://docs.python.org/3.4/faq/programming.html#what-s-up-with-the-comma-operator-s-precedence - Chris_Rands
48
文档可以声称任何他们想要的,但那并不重要。您可以编写解析器,使每个运算符都有自己的产生式,并且在实现中没有明确的“优先级”,但这并不能阻止这些句法单元成为运算符。您可以以某种特定于实现的方式重新定义“运算符”,这显然是Python中所做的,但这并不改变术语的含义。逗号有效地是一个产生元组的运算符。例如,其相对优先级受括号影响,它的运算符性质就表现出来了。 - Mark Reed

69
在所有3个示例中看到的都是语言的语法规范的结果,以及在源代码中遇到的标记如何解析以生成解析树。
查看这个低级别的代码应该可以帮助您了解底层发生了什么。我们可以将这些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

21

另一种解释该问题的方式:你可能熟悉字典字面量。

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

和数组字面量

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

以及元组字面值

( 1, 2, 3 )

但是你可能没有意识到的是,与字典和数组文字不同,通常在元组文字周围看到的括号不是语法的一部分。元组文字的语法仅仅是由逗号分隔的表达式序列:

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

17

在执行操作的顺序周围加上一对括号可能会帮助您更好地理解结果:

# 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 构成的表达式。


6
在第一个中,Python将两个元素组成了一个元组:
  1. 表达式(0, 0) == 0,其求值为False
  2. 常量0
在第二个中则相反。

0

看这个例子:

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)。


0

我有一个类似的问题。我不是计算机科学家,我是软件工程师或计算机程序员。所以我问了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。

我想感谢您将这个问题引起我的注意。这是一个很好的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接