整数对象标识测试:大正负整数之间的不一致行为

时间:2017-04-30 20:47:23

标签: python python-internals

我使用的是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运行和交互式运行之间的不一致给出了很好的解释。但是仍然要回答我关于交互式运行中大正整数和小负整数之间不一致的问题。

3 个答案:

答案 0 :(得分:5)

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

但是,在翻译中,情况有所不同。这里,只有一行/语句一次运行。为每个新行创建一个特定常量。它不会在下一行中重复使用。所以,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是不同的。

希望现在有意义,

答案 1 :(得分:2)

当您在交互式shell中运行1000 is 1000或作为较大脚本的一部分时,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
  • 使用isTrue如果操作数引用同一个对象,则进行比较;否则为False
  • 返回结果

作为CPython only creates one Python object for a constant used in a code block1000 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有一个窥视孔优化器,能够通过在编译后立即对它们进行评估来优化涉及常量的简单表达式,并将结果存储在常量表中。

从CPython 3.6开始,折叠一元操作由Python/peephole.c中的fold_unaryops_on_constants处理。特别是,-(一元减号)由PyNumber_Negative计算,它返回一个新的Python对象(-6 is not cached)。之后,新创建的对象将插入consts表。但是,优化器不检查表达式的结果是否可以重用,因此相同表达式的结果最终成为不同的Python对象(同样,从CPython 3.6开始)。

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

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

-6元组中有两个co_consts常量

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对象,即使您用其他内容替换x is yTrue也会返回-6

答案 2 :(得分:0)

为了补充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

很可能您会看到&#39; x&#39;并且&#39; y&#39;然后在解释器中运行代码,ids将有所不同。

>x=1000
>y=1000

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

See Here