整数对象身份测试:大正整数和小负整数之间的不一致行为

Integer object identity test: inconsistent behavior between large positive and small negative integers

我正在使用 Anaconda (Python 3.6)。

在交互模式下,我对 >256 的正整数进行了对象身份测试:

# Interactive test 1
>>> x = 1000
>>> y = 1000
>>> x is y
False

很明显,大整数(>256)写在单独的行中不会在交互模式下被重用。

但是如果我们把赋值写在一行中,大的正整数对象会被重用:

# Interactive test 2
>>> x, y = 1000, 1000
>>> x is y
True

也就是说,在交互模式下,将整数赋值写在一行或单独的行中会对重用整数对象 (>256) 产生影响。对于 [-5,256] 中的整数(如 https://docs.python.org/2/c-api/int.html 所述),缓存机制确保只创建一个对象,无论赋值是在同一行还是不同行。

现在让我们考虑小于-5的小负整数(任何超出范围[-5, 256]的负整数都可以达到目的),令人惊讶的结果出来了:

# Interactive test 3
>>> x, y = -6, -6
>>> x is y
False     # inconsistent with the large positive integer 1000

>>> -6 is -6
False

>>> id(-6), id(-6), id(-6)
(2280334806256, 2280334806128, 2280334806448)

>>> a = b =-6
>>> a is b
True    # different result from a, b = -6, -6

显然,这表明大正整数 (>256) 和小负整数 (<-5) 之间的对象身份测试不一致。而对于小的负整数(<-5),写成a,b=-6,-6和a=b=-6的形式也有区别(相比之下,对于大整数使用哪种形式并不重要) ).对这些奇怪的行为有什么解释吗?

为了比较,让我们继续 IDE 运行(我使用 PyCharm 和相同的 Python 3.6 解释器),我 运行以下脚本

# IDE test case
x = 1000
y = 1000
print(x is y) 

它打印 True,不同于交互式 运行。感谢@Ahsanul Haque,他已经很好地解释了 IDE 运行 和交互式 运行 之间的不一致。但是还是要回答我关于交互中大正整数和小负整数不一致的问题运行。

仅为特定源代码创建特定常量的一个副本,并在需要时重复使用。因此,在 pycharm 中,您得到 x is y == True.

但是,在解释器中,情况有所不同。在这里,一次只有一个 line/statement 运行。为每一行创建一个特定的常量。它不会在下一行中重复使用。所以,x is not y 在这里。

但是,如果您可以在同一行中进行初始化,则可以有相同的行为(重复使用相同的常量)。

>>> x,y = 1000, 1000
>>> x is y
True
>>> x = 1000
>>> y = 1000
>>> x is y
False
>>> 

编辑:

块是作为一个单元执行的一段Python程序文本。

在IDE中,整个模块立即执行,即整个模块是一个块。但在交互模式下,每条指令实际上是一次性执行的一段代码。

正如我之前所说,一个特定的常量只为一段代码创建一次,如果再次出现在该代码块中则可以重复使用。

这是IDE和解释器之间的主要区别。

那么,为什么对于较小的数字,解释器实际上给出与 IDE 相同的输出?这是在考虑整数缓存的时候。

如果数字较小,则将它们缓存起来并在下一个代码块中重复使用。所以,我们在 IDE 中得到了相同的 id。

但如果它们更大,则不会缓存它们。而是创建了一个新副本。因此,正如预期的那样,id 不同。

希望这现在有意义,

为了补充 Ahsanul Haque 的答案,在任何 IDE:

中尝试这个
x = 1000
y = 1000
print (x is y)
print('\ninitial id x: ',id(x))
print('initial id y: ',id(y))

x=2000
print('\nid x after change value:   ',id(x))
print('id y after change x value: ', id(y))

initial id x:  139865953872336
initial id y:  139865953872336

id x after change value:    139865953872304
id y after change x value:  139865953872336

您很可能会看到 'x' 和 'y' 相同的 ID,然后 运行 解释器中的代码和 ID 将不同。

>x=1000
>y=1000

>id(x)
=> 139865953870576
>id(y)
=> 139865953872368

See Here.

当您 运行 1000 is 1000 在交互式 shell 中或作为更大脚本的一部分时,CPython 会生成类似

的字节码
In [3]: dis.dis('1000 is 1000')
   ...: 
  1           0 LOAD_CONST               0 (1000)
              2 LOAD_CONST               0 (1000)
              4 COMPARE_OP               8 (is)
              6 RETURN_VALUE

它的作用是:

  • 加载两个常量(LOAD_CONST 将 co_consts[consti] 压入堆栈 -- docs
  • 使用 is 比较它们(True 如果操作数引用同一对象;False 否则)
  • Returns 结果

由于 1000 is 1000 将导致创建单个整数常量:

In [4]: code = compile('1000 is 1000', '<string>', 'single') # code object

In [5]: code.co_consts # constants used by the code object
Out[5]: (1000, None)

根据上面的字节码,Python 将加载同一个对象两次并将其与自身进行比较,因此表达式的计算结果为 True:

In [6]: eval(code)
Out[6]: True

-6的结果不同,因为-6不会立即被识别为常量:

In [7]: ast.dump(ast.parse('-6'))
Out[7]: 'Module(body=[Expr(value=UnaryOp(op=USub(), operand=Num(n=6)))])'

-6 是对整数文字 6.

的值求反的表达式

然而,-6 is -6 的字节码实际上与第一个字节码示例相同:

In [8]: dis.dis('-6 is -6')
  1           0 LOAD_CONST               1 (-6)
              2 LOAD_CONST               2 (-6)
              4 COMPARE_OP               8 (is)
              6 RETURN_VALUE

因此 Python 加载两个 -6 常量并使用 is.

比较它们

-6表达式如何变成常量? CPython 有一个窥孔优化器,能够优化涉及常量的简单表达式,方法是在编译后立即对它们求值,并将结果存储在常量的 table 中。

从 CPython 3.6 开始,折叠一元运算由 Python/peephole.c. In particular, - (unary minus) is evaluated by PyNumber_Negative that returns a new Python object (-6 is not cached 中的 fold_unaryops_on_constants 处理。之后,新创建的对象被插入到conststable。但是,优化器不会检查表达式的结果是否可以重用,因此相同表达式的结果最终会成为不同的 Python 对象(同样,从 CPython 3.6 开始)。

为了说明这一点,我将编译 -6 is -6 表达式:

In [9]: code = compile('-6 is -6', '<string>', 'single')

co_consts 元组中有两个 -6 常量

In [10]: code.co_consts
Out[10]: (6, None, -6, -6)

而且它们有不同的内存地址

In [11]: [id(const) for const in code.co_consts if const == -6]
Out[11]: [140415435258128, 140415435258576]

当然,这意味着 -6 is -6 的计算结果为 False:

In [12]: eval(code)
Out[12]: False

在大多数情况下,上述解释在存在变量的情况下仍然有效。在交互中执行时shell,这三行

>>> x = 1000
>>> y = 1000
>>> x is y
False

是三个不同代码块的一部分,因此 1000 常量不会被重用。但是,如果将它们全部放在一个代码块中(如函数体),常量将被重用。

相比之下,x, y = 1000, 1000 行总是在一个代码块中执行(即使在交互式 shell 中),因此 CPython 总是重用常量。在 x, y = -6, -6 中,-6 没有被重复使用,原因在我的回答的第一部分中解释过。

x = y = -6 是微不足道的。由于只涉及一个 Python 对象,即使您将 -6 替换为其他对象,x is y 也会 return True