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



我正在使用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(,Caching机制可确保仅创建一个对象,无论分配是否在同一分配中或不同的行。

现在,让我们考虑小于-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运行(我正在使用具有同一Python 3.6解释器的Pycharm(,我运行以下脚本

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

它打印为真,与交互式运行不同。多亏了@Ahsanul Haque,他已经对IDE Run和Interactive Run之间的不一致做出了很好的解释。但是,仍然需要回答我关于互动式运行中大整数和小型整数之间不一致的问题。

仅创建一个特定常数的一个副本,为特定的源代码创建,并在需要时重复使用。因此,在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是不同的。

希望这是有道理的,

当您在交互式外壳中运行 1000 is 1000或作为较大脚本的一部分时,cpython会生成bytecode,例如

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比较它们(如果操作数请参考同一对象; False否则(
  • 返回结果

由于CPYTHON仅创建一个用于代码块中使用的常数的Python对象,因此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具有窥视孔优化器,能够通过在编译后立即评估涉及常数的简单表达式,并将结果存储在常数中。

从CPYTHON 3.6开始,Python/peephole.c中的fold_unaryops_on_constants处理折叠单操作。特别是,-(Unary Minus(由PyNumber_Negative评估,该CC_21返回一个新的Python对象(未缓存-6(。之后,将新创建的对象插入到consts表中。但是,优化器不检查表达式的结果是否可以重复使用,因此相同表达式的结果最终是不同的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

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

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

是三个不同代码块的一部分,因此1000常数不会被重复使用。但是,如果将它们全部放在一个代码块(如功能主体(中,则将重复使用常数。

相比之下,x, y = 1000, 1000线始终在一个代码块中执行(甚至在交互式外壳中(,因此CPYTHON始终重复使用常数。在x, y = -6, -6中,-6由于我答案的第一部分所解释的原因而没有重新使用。

x = y = -6是微不足道的。由于完全涉及一个Python对象,x is y即使您用其他东西替换-6也会返回True

以补充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"one_answers" y"的同一ID,然后在解释器中运行代码,ID会不同。

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

请参阅此处。

最新更新