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

最佳答案

当您在交互式 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 开始,折叠一元操作由 fold_unaryops_on_constants 中的 Python/peephole.c 处理。特别是,-(一元减号)由 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 y 替换为其他内容,True 也会返回 -6

    关于python - 整数对象身份测试 : inconsistent behavior between large positive and small negative integers,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43711154/

    10-12 22:43