Python 3.6 及更早版本与 3.7 中不可变对象的"is"行为不一致



我在向我的学生介绍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版本中是不对的。

有人知道发生了什么变化来解释这种新行为吗?

我不确定原因和来源,但我猜测这与在线优化有关。

如果您要为这些值分配变量,身份检查将产生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

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

这种期望是值得怀疑的——Python语言无法保证这一点。

is是一个相当棘手的运算符,因为你真的需要知道什么时候使用它是合适的

例如:

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

这些在一般情况下不适合使用is。如果你想检查Python在做什么行/函数优化(interning),它们可能是合适的,但我想这不是这里想要的用例。

只有当您想与常量(保证只有一个实例)进行比较时,才应使用is!保证的内置常数为:

  • 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

有人知道做出了什么改变来解释这种新行为吗?

可能窥视孔优化器现在优化了更多的情况(元组和数学表达式)。例如"AST水平恒定折叠"(https://bugs.python.org/issue29469)已经添加到CPython 3.7中(我有意在这里编写CPython,因为它没有添加到Python 3.7语言规范中)。

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

回复:

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

不可更改性与具有不可更改的值并不完全相同。在调用可变或不可变对象之前,它是一个对象,Python中的对象是在运行时创建的。因此,没有理由将可变性与对象创建和标识联系起来。然而,也有一些例外,比如这一个,或者在以前和当前版本中都有小对象,主要是为了优化,这个规则(在运行时创建对象)被操纵了。阅读https://stackoverflow.com/a/38189759/2867928了解更多详细信息。

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

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

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

相关内容

最新更新