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

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

最佳答案 当您在交互式shell中运行1000是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)
>使用is比较它们(如果操作数引用同一个对象,则为True;否则为False)
>返回结果

如果是CPython only creates one Python object for a constant used in a code block,1000是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的字节码-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是-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是-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也会返回True.

点赞