【问题标题】:Why in Python does "0, 0 == (0, 0)" equal "(0, False)"?为什么在 Python 中 "0, 0 == (0, 0)" 等于 "(0, False)"?
【发布时间】:2017-07-01 18:29:11
【问题描述】:

在 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

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

【问题讨论】:

    标签: python tuples operator-precedence


    【解决方案1】:

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

    1. (0, 0) == 0(即False),后跟0
    2. 0,然后是 0 == (0, 0)(仍然是 False)。

    由于逗号分隔符与相等运算符相比具有相对优先级,因此表达式被拆分:Python 看到一个包含两个表达式的元组,其中一个恰好是相等测试,而不是两个元组之间的相等测试.

    但是在你的第二组语句中,a = 0, 0不能是一个元组。元组是值的集合,与相等测试不同,赋值在 Python 中没有值。赋值不是表达式,而是语句;它没有可以包含在元组或任何其他周围表达式中的值。如果您尝试使用 (a = 0), 0 之类的方法来强制解释为元组,则会出现语法错误。这使得元组对变量的赋值——可以通过写成a = (0, 0)来更明确——作为a = 0, 0的唯一有效解释。

    因此,即使在分配给a 时没有括号,它和b 都被分配了值(0,0),因此a == bTrue

    【讨论】:

    • 我会说逗号运算符的优先级低于相等,因为相等的评估先于逗号运算符的评估:相等的优先级高于逗号运算符。但这总是令人困惑的根源。只是想指出其他来源可能会扭转局面。
    • 您可以通过说, 的绑定不如== 紧密来避免较低/较高的措辞混淆。
    • 逗号不是运算符docs.python.org/3.4/faq/…
    • 文档可以声称他们想要的一切,但这并不重要。您可以编写一个解析器,以便每个运算符都有自己的生产,并且在实现中的任何地方都没有明确的“优先级”,但这并不能阻止这些句法单元成为运算符。您可以以某种特定于实现的方式重新定义“运算符” ,这显然是他们在 Python 中所做的,但这并没有改变该术语的含义。逗号实际上是一个产生元组的运算符。它的操作性表现在,例如,它的相对优先级受括号影响的方式。
    【解决方案2】:

    您在所有 3 个实例中看到的是该语言的 grammar specification 的结果,以及如何解析源代码中遇到的标记以生成解析树。

    看看这个低级代码应该可以帮助您了解幕后发生的事情。我们可以将这些 python 语句转换成字节码,然后使用dis 模块进行反编译:

    案例一:(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

    【讨论】:

      【解决方案3】:

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

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

      和数组字面量

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

      和元组字面量

      ( 1, 2, 3 )
      

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

      1, 2, 3
      

      formal grammar for 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
      

      【讨论】:

        【解决方案4】:

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

        # 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
        

        逗号用于分隔表达式(当然,使用括号我们可以强制执行不同的行为)。查看您列出的 sn-ps 时,逗号 , 会将其分隔并定义要评估的表达式:

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

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

        【讨论】:

          【解决方案5】:

          在第一个中,Python 正在创建一个包含两件事的元组:

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

          在第二个中,情况正好相反。

          【讨论】:

            【解决方案6】:

            看看这个例子:

            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)进行比较。

            【讨论】:

              【解决方案7】:

              我也有类似的问题。我不是计算机科学家,我要么是软件工程师,要么是计算机程序员。所以我问了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,它也是 not False。

              感谢您提请我注意这个问题。这是一个很好的问题。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-05-01
                • 2015-10-16
                • 2012-11-11
                • 1970-01-01
                • 1970-01-01
                • 2022-01-23
                相关资源
                最近更新 更多