首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >整数对象身份检验:大正整数和小负整数之间的不一致行为

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

Stack Overflow用户
提问于 2017-04-30 20:47:23
回答 3查看 542关注 0票数 3

我正在使用Anaconda (Python3.6)。

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

代码语言:javascript
复制
# Interactive test 1
>>> x = 1000
>>> y = 1000
>>> x is y
False

显然,大整数(>256)在单独的行中写入不能在交互模式中重用。

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

代码语言:javascript
复制
# Interactive test 2
>>> x, y = 1000, 1000
>>> x is y
True

也就是说,在交互模式下,将整数赋值写入一行或单独行将对重用整数对象(>256)产生影响。对于-5 256中的整数,缓存机制确保只创建一个对象,无论赋值是否位于相同或不同的行中。

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

代码语言:javascript
复制
# 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 run (我使用的是同一个Python3.6解释器的PyCharm ),我运行以下脚本

代码语言:javascript
复制
# IDE test case
x = 1000
y = 1000
print(x is y) 

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-05-01 00:17:15

当您在交互式shell中或作为更大脚本的一部分运行1000 is 1000时,CPython生成字节码如下

代码语言:javascript
复制
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_constsconsti推到堆栈上- 文档)
  • 使用is比较它们(如果操作数引用同一个对象,则为True;否则为False )
  • 返回结果

作为CPython只为代码块中使用的常量创建一个Python对象。1000 is 1000将导致创建一个整数常量:

代码语言:javascript
复制
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

代码语言:javascript
复制
In [6]: eval(code)
Out[6]: True

对于-6,结果是不同的,因为-6 不能立即被识别为常量

代码语言:javascript
复制
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的字节码实际上与第一个字节码示例相同:

代码语言:javascript
复制
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开始,折叠一元操作由fold_unaryops_on_constantsPython/peephole.c中处理。特别是,- (一元减号)由返回新Python对象(不缓存)的PyNumber_Negative计算。之后,新创建的对象被插入到consts表中。但是,优化器不检查表达式的结果是否可以重用,因此相同表达式的结果最终是不同的CPython对象(同样,在Python3.6中)。

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

代码语言:javascript
复制
In [9]: code = compile('-6 is -6', '<string>', 'single')

-6元组中有两个co_consts常量

代码语言:javascript
复制
In [10]: code.co_consts
Out[10]: (6, None, -6, -6)

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

代码语言:javascript
复制
In [11]: [id(const) for const in code.co_consts if const == -6]
Out[11]: [140415435258128, 140415435258576]

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

代码语言:javascript
复制
In [12]: eval(code)
Out[12]: False

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

代码语言:javascript
复制
>>> 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 y将返回True,即使您用其他东西替换了-6

票数 2
EN

Stack Overflow用户

发布于 2017-04-30 20:54:45

只为特定的源代码创建一个特定常量的副本,并在需要时进一步重用。所以,在“侏儒”中,你得到了x is y == True

但是,在翻译中,情况就不一样了。在这里,一次只运行一行/语句。为每一行创建一个特定的常量。它不会在下一行中被重用。所以,x is not y在这里。

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

代码语言:javascript
复制
>>> x,y = 1000, 1000
>>> x is y
True
>>> x = 1000
>>> y = 1000
>>> x is y
False
>>> 

编辑:

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

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

正如我前面所说,为代码块创建一次特定的常量,如果再次出现在该代码块中,则重新使用。

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

那么,为什么实际上解释器为较小的数字提供了与IDE相同的输出?这时,需要考虑整数缓存。

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

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

希望这是有意义的,

票数 5
EN

Stack Overflow用户

发布于 2017-04-30 21:14:36

为了补充Ahsanul Haque的回答,可以在任何IDE中尝试这样做:

代码语言:javascript
复制
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就会不同。

代码语言:javascript
复制
>x=1000
>y=1000

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

看这里

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43711154

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档