【问题标题】:Why does the 'is' operator behave unexpectedly with arithmetically equal expressions为什么'is'运算符在算术相等的表达式中表现出意外
【发布时间】:2021-05-25 00:23:06
【问题描述】:

阅读thisthis后,我仍然无法理解以下行为:

a = 1000
b = 1000
print (a == b)
print (a is b)
print (f"id(a) = {id(a)} \nid(b) = {id(b)}")

正如所料,我得到了

True
True
id(a) = 2806705928816 
id(b) = 2806705928816

但是当我尝试做这样的事情时:

a = 1000
b = 1000 + a - a
print (a == b)
print (a is b)
print (f"id(a) = {id(a)} \nid(b) = {id(b)}")

我在表达式a is b 中得到了False

True
False
id(a) = 3030783801968 
id(b) = 3030783802064

为什么在将表达式的结果分配给整数和带有其他变量的表达式时,变量的行为会有所不同?尽管在数学上这给出了相同的整数。

【问题讨论】:

  • 因为这两个 1000 是字面量。它们是在程序开始时定义的。只需要其中一个。
  • 第二种情况,它们不是“同一个整数”;它们是具有相同不同整数对象。
  • 究竟是什么意外?为什么你期望 a is b在这里是真的?在您的第一个示例中这是正确的事实应该令人惊讶
  • 不要使用is 来检查算术相等性,这就是== 的用途。 is 用于检查两个变量/名称是否引用同一个对象
  • 这完全是猜测,但我猜这只是编译器优化有多聪明的问题。大多数时候,您并不关心 Python 如何在后台分配变量内存,但在第一种情况下,它通过避免重复分配来尝试节省空间,这对您来说是透明的,如果您不是观看id() 值。但在第二种情况下,它不够聪明,所以你最终得到了 2 个分配。

标签: python python-3.x identity equality cpython


【解决方案1】:

区别在于对位置的引用。 '==' 检查数据类型和值是否相等,但是,'is;引用变量在内存中的位置。

下面会返回 false

id(a) = 3030783801968 <----
id(b) = 3030783802064 <----

is 将在下面返回 true

    id(a) = 2806705928816 <----
    id(b) = 2806705928816 <----

【讨论】:

  • 我可能会感到困惑,但我认为这个问题是在正确理解==is之间的区别的情况下提出的。问题是为什么 Python 在一种情况下分配相同的 id/地址而不在另一种情况下分配。
  • 在第二种情况下,您通过添加 'a-a' 进行了“附加算术”因此,python 然后将值存储在另一个位置
【解决方案2】:

当您执行以下操作时:

(案例一)

a = 1000
b = a

或(案例2)

a = 1000
b = 1000

Python 足够聪明,可以预先知道即使在执行之后您也不需要新的内存。

因此,python 在执行前将b 在第一种情况下设为a 的别名。

第二种情况有点不同。 Python 是一种真正的面向对象语言,文字 1000 被视为对象。 (直觉上你可以认为 1000 是一个 const 对象的名称)。

所以在第二种情况下,ab 在技术上都是 alias1000

现在在你的例子中:

a = 1000
b = 1000 + a - a
print (a == b)
print (a is b)

在分配b 时,python 事先并不知道a 的值是什么。当我说事先我的意思是在任何形式的计算开始之前。所以python为b预留了一个新的内存位置,然后将操作的输出保存在这个新的内存位置。

还有一点值得注意:

4-1 is 3
True

在这种情况下,python 不会将此行保存为4-1,而是在编译之前将其处理为3,以进行运行时优化。

【讨论】:

    【解决方案3】:

    Python 通过将其表达式逐个评估为值来执行语句,然后对这些值执行一些操作。

    来源: https://courses.cs.washington.edu/courses/cse140/13wi/eval_rules.pdf

    基本上 b = 1000 + a - a 不是一次性完成的,而是在多次评估中,python 在每次评估时将 b 的结果存储在与 a 不同的内存位置。此时a和b是不同的对象。

    使用 == 进行相等检查。

    使用“is”检查对象是否相同(变量引用相同的内存位置)。

    【讨论】:

      【解决方案4】:

      您已经有了一些准确的答案。在这里,我给出一个“回归基础”的答案。


      什么是==

      Python == 表示左边的值和右边的值一样

      sum([5, 7]) == (48 * 3)**0.5
      

      True。它需要几个评估步骤才能使每个表达式达到12 的值。即便如此,integer 12 正在与 float 12.0 进行比较,因此需要将整数最终转换为浮点数。

      关键要点:每个表达式都经过评估,并比较生成的。如果它们相等,则表达式为真。

      什么是is

      另一方面,Python is 意味着 左边的名字与右边的名字指向同一个对象

      a = 3.14159
      b = a
      a is b
      

      Truea 已分配给值 3.14159。但更重要的是,有一块内存保存着一个对象,在本例中是浮点数 3.14159。 a 指向该对象/内存块。 b 指向a,这意味着它指向同一块内存


      您可以很容易地对此进行测试:创建两个简单地指向一个数字的“名称”,并使用is 比较它们,它们将不匹配:

      >>> a = 1239481203948
      >>> b = 1239481203948
      >>> a is b
      False
      

      这是错误的,因为我们现在在内存/对象中有两个不同的位置分别指向它们:

      >>> id(a)
      140402381635344
      >>> id(b)
      140402391174416
      

      (在您的机器上,您将获得一组不同的ids。)

      因此,实际上,您“浪费”了空间,因为您有两个对象为相同的信息占用空间。

      但是等等,还有更多

      如果你自己玩这个,你会发现我写的东西有的例外,你会感到困惑。这里只是一些:

      >>> a = 157
      >>> b = 157
      >>> a is b
      True
      

      什么?为什么这是真的?为了优化 Python,对“最常见的数字”进行了优化。我可能是错的,但我记得在内存中为最常见的数字指定了空间。这些是前几百个整数,还有其他几个。

      但也有其他优化:

      >>> a = None
      >>> b = None
      >>> a is b
      True
      
      >>> a = True
      >>> b = True
      >>> a is b
      True
      

      这些仍然遵循我之前所说的相同规则:is 评估为True 的原因是因为ab 都指向内存/对象中的相同位置。

      由于 Python 中的优化,在这些奇怪的情况下会发生这种情况。 但一般来说,确保 is 评估为 True 的唯一方法是,如果将名称分配给已经有名称的对象,就像我们写的那样:

      >>> a = 3.14159
      >>> b = a
      >>> a is b
      True
      

      而不是写

      >>> a = 3.14159
      >>> b = 3.14159
      >>> a is b
      False
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-25
        • 2013-08-08
        • 2017-01-13
        • 2019-05-29
        相关资源
        最近更新 更多