【问题标题】:Integer object identity test: inconsistent behavior between large positive and small negative integers整数对象身份测试:大正整数和小负整数之间的不一致行为
【发布时间】:2017-09-28 09:34:53
【问题描述】:

我正在使用 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) 和小负整数 (

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

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

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

【问题讨论】:

  • 根据Learning Python 5e,整数捕获仅适用于“小整数”。我想知道如何缓存小整数,这就是我测试上述内容的原因。我的问题是为什么我从交互模式和 IDE 运行中得到不一致的结果。哪个结果是正确的?
  • 是的,我几乎立即撤回了我的近距离投票。我扫描你的问题太快了,我的错。
  • 谢谢。当我看到你撤回我的评论时,我也删除了我的评论。谢谢你的帮助。你提供的那个链接对我很有帮助。
  • @vaultah 我确定我的问题现在与您指出的问题不同。我已经完全重写了我的问题,其中我描述了两种不同的奇怪行为,您提供的链接没有涵盖它们。

标签: python python-internals


【解决方案1】:

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

希望现在有意义,

【讨论】:

  • 谢谢。你的解释似乎很有道理。但我仍然很困惑。如果解释器(在交互模式下)将单独行中的两个整数常量视为不同的对象,它如何设法将单独行中的两个较小的整数视为同一个对象?我知道有一个称为整数缓存的概念,它适用于小整数。但我只是不明白为什么写在相同或不同的行中很重要?在 IDE 中是否在同一行或不同行中都没有关系。是什么导致 IDE 和交互模式如此不一致?
  • 看起来即使在交互模式下写在同一行,也可能会生成两个不同的对象,例如x, y = -6, -6, x is y 变为False。但是 x,y = 1000, 1000, x 是 y 是 True。即使在相同的交互模式下,由于这种不一致的结果,我仍然很困惑,请注意 -6 和 1000 都超出了 [-5, 256],这被描述为缓存工作的整数范围(参见 docs.python.org/2/c-api/int.html) .
  • 我刚刚注意到,即使在 IDE 中,也不能重用小的负整数对象 (
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    当您在交互式 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
    • 使用is 比较它们(True 如果操作数引用相同的对象;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')
    

    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 对象,因此 x is y 将返回 True,即使您将 -6 替换为其他对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-18
      • 1970-01-01
      • 2016-02-15
      • 2015-10-01
      • 1970-01-01
      相关资源
      最近更新 更多