【问题标题】:inconsistency in "is" behavior over immutable objects in python 3.6 and older vs 3.7python 3.6 和更早版本与 3.7 中不可变对象的“is”行为不一致
【发布时间】:2019-02-08 15:21:11
【问题描述】:

我正在向我的学生介绍 is 运算符时,我注意到它的行为在 python(v3.6 和更早版本)和 (v3.7) 之间存在不一致。

启动 python shell 并运行:

5/2 is 2.5

或者:

(1, 2, 3) is (1, 2, 3)

在 v3.6.X 中,两者都得到 False,但在 v3.7 中,它们变成了 True

我的期望是结果应该是 True,因为我认为不可变数字对象(或它们的元组)只有一个实例。

看来至少我的想法在以前的Python版本中是不对的。

有谁知道做出了哪些改变来解释这种新行为?

【问题讨论】:

  • 在以前版本的 pyhton 中,浮点数不会被保留,这与小整数不同。元组也不是。
  • 他们可能决定实习更多的东西。那又怎样?
  • 我永远不会期望任何两个值是同一个对象,除非我自己明确地重新分配了对象 (a = b; a is b)。其他任何事情都取决于特定实现的内部优化过程。
  • 不要编写依赖于产生相同对象的相等文字的代码。除非你自己在某个地方写了x = y,否则永远不要假设x is y 可能是真的。
  • 您只能安全地实习不可变对象,但这并不意味着所有不可变对象都已(或应该)实习。

标签: python python-3.x internals


【解决方案1】:

我不确定原因和来源,但我的猜测是这与内联优化有关。

如果您为此值分配变量,身份检查将产生False,与之前相同。

>>> 5/2 is 2.5
True
>>> a = 5/2
>>> a is 2.5
False

关于新折叠优化的有趣说明。由于 python 是“所有运行时”,因此无法优化前面的某些内容,但它会努力解析尽可能多的范围:

>>> a = 3.14
>>> b = 3.14
>>> a is b
False
>>> a = 3.14; b = 3.14
>>> a is b
True

【讨论】:

    【解决方案2】:

    我的期望是结果应该是 True,因为我认为不可变数字对象(或它们的元组)只有一个实例。

    这种期望是有问题的 - Python 语言无法保证这样的事情。

    is 是一个非常棘手的运算符,因为您确实需要知道何时适合使用它。

    例如:

    >>> 5 / 2 is 2.5
    >>> (1, 2, 3) is (1, 2, 3)
    

    在一般情况下,这些是 is 的不恰当用法。如果您想检查 Python 正在做什么行/函数优化(实习),它们可能是合适的,但我想这不是这里所需的用例。

    is 仅应在您想与常量进行比较时使用(保证只有一个实例)! guaranteed built-in constants 是:

    • None
    • NotImplemented
    • Ellipsis(也称为...
    • True
    • False
    • __debug__

    或者你自己的类常量实例:

    _sentinel = object()
    
    def func(a=_sentinel):
        return a is _sentinel
    

    或者当您明确地将变量分配给新名称时:

    a = b
    a is b  # <- that's expected to be True
    

    有谁知道做出了哪些改变来解释这种新行为?

    可能窥孔优化器现在可以优化更多情况(元组和数学表达式)。例如,CPython 3.7 中添加了“AST 级常量折叠”(https://bugs.python.org/issue29469)(我故意在这里写了 CPython,因为它没有添加到 Python 3.7 语言规范中)。

    【讨论】:

    • 这应该是链接到所有is 问题的唯一答案。
    【解决方案3】:

    我相信这种行为是由于将常量折叠从窥视孔优化器(编译时操作)移动到新的 AST 优化器(运行时操作),正如 https://docs.python.org/3/whatsnew/3.7.html#optimizations 中提到的那样,它现在能够执行更多优化始终如一。 (由 Eugene Toder 和 INADA Naoki 在bpo-29469bpo-11549 提供。)

    回复:

    我的期望是结果应该是 True,因为我认为不可变数字对象(或它们的元组)只有一个实例。

    不变性与具有不可更改的值严格来说并不相同。在您调用对象可变或不可变之前,它是一个对象,Python 中的对象是在运行时创建的。因此,没有理由将可变性与对象创建和身份联系起来。但是,在以前的版本和当前版本中都有一些例外,例如这个或小对象实习,主要是为了优化,这个规则(在运行时创建对象)被操纵。阅读https://stackoverflow.com/a/38189759/2867928了解更多详情。

    【讨论】:

      【解决方案4】:

      为什么相同的不可变对象要占用同一个实例?

      在 python 中使用is 时,您实际上是在询问ab 是否占用内存中的同一块。如果您将ab 视为不可变文字,那么python 并没有特定的空间来保存每种类型的不可变文字。在这种情况下,它完全有可能返回 true,如果您选择不同的文字,它完全有可能返回 false。看看this

      >>> a = "wtf"
      >>> b = "wtf"
      >>> a is b
      True
      
      >>> a = "wtf!"
      >>> b = "wtf!"
      >>> a is b
      False
      
      >>> a, b = "wtf!", "wtf!"
      >>> a is b
      True
      

      如果您想避免这种情况,请不要在未明确保存到内存中的内容上使用 is

      【讨论】:

      • afaik,is 与内存分配无关。 Python 声明is 代表身份检查。在 CPython 中,内存地址(曾经?)用作 id 基址,但实际上在不同的实现中没有任何东西可以对此进行规范。
      • 据我了解,id本质上是python对C内存地址的实现。它只是不会增加对象的大小,因为它是单独存储的。见this
      • 这是一个实现细节。只要 VM 为对象提供唯一的 id,就可以了。无需直接将其实际映射到内存
      猜你喜欢
      • 1970-01-01
      • 2019-07-20
      • 2019-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多