在python中IS运算符的罕见行为

时间:2018-06-15 19:33:54

标签: python

从Stackoverflow的一些答案中,我开始知道从-5到256相同的内存位置被引用,因此我们得到了:

>>> a = 256
>>> a is 256
True

现在出现了扭曲(在标记重复之前请参阅此行):

>>> a = 257
>>> a is 257 
False 

这是完全理解的,但现在如果我这样做:

>>> a = 257; a is 257
True
>>> a = 12345; a is 12345
True

为什么?

4 个答案:

答案 0 :(得分:7)

您所看到的是CPython中编译器的优化(将源代码编译为解释器运行的字节码)。每当在一个步骤中编译的一块代码中的几个不同位置使用相同的不可变常量值时,编译器将尝试对每个位置使用对同一对象的引用。

因此,如果您在交互式会话中对同一行进行多项分配,您将获得对同一对象的两次引用,但如果您使用两条不同的行,则不会获胜:

>>> x = 257; y = 257  # multiple statements on the same line are compiled in one step
>>> print(x is y)     # prints True
>>> x = 257
>>> y = 257
>>> print(x is y)     # prints False this time, since the assignments were compiled separately

此优化出现的另一个地方是函数体。整个函数体将被编译在一起,因此函数中任何地方使用的任何常量都可以组合,即使它们在不同的行上:

def foo():
    x = 257
    y = 257
    return x is y  # this will always return True

虽然调查像这样的优化很有意思,但你不应该在普通代码中依赖这种行为。不同的Python解释器,甚至不同版本的CPython可以以不同方式进行这些优化,或者根本不进行。如果您的代码依赖于特定的优化,那么对于试图在自己的系统上运行它的其他人来说,它可能会完全被破坏。

作为一个例子,我在上面第一个代码块中显示的同一行上的两个赋值,当我在Spyder里面的交互式shell(我首选的IDE)中执行它时,不会导致对同一个对象的两个引用。我不知道为什么这种特定的情况不像传统的交互式shell那样工作,但不同的行为是我的错,因为我的代码依赖于特定于实现的行为。

答案 1 :(得分:1)

经过各种版本的讨论和测试,可以得出最终结论。

Python将以块的形式解释和编译指令。根据所使用的语法,Python版本,操作系统,分发,可以实现不同的结果,具体取决于Python在一个块中采用的指令。

一般规则是:

(来自official documentation

  

当前实现为所有实体保留了一个整数对象数组   -5到256之间的整数

因此:

a = 256
id(a)
Out[2]: 1997190544
id(256)
Out[3]: 1997190544 # int actually stored once within Python

a = 257
id(a)
Out[5]: 2365489141456
id(257)
Out[6]: 2365489140880 #literal, temporary. as you see the ids differ
id(257)
Out[7]: 2365489142192 # literal, temporary. as you see it gets a new id everytime
                      # since it is not pre-stored

以下部分在 Python 3.6.3 | Anaconda custom(64位)|中返回False (默认,2017年10月17日,23:26:12)[MSC v.1900 64 bit(AMD64)]

a = 257; a is 257
Out[8]: False

但是

a=257; print(a is 257) ; a=258; print(a is 257)
>>>True
>>>False

很明显,无论Python在“一个块”中采用什么都是非确定性的,并且可以根据它的编写方式,单行与否,以及所使用的版本,操作系统和分布而摇摆不定。

答案 2 :(得分:1)

来自python2 docs:

  

运算符是和不是对象标识的测试:x是y是真的   当且仅当x和y是同一个对象时。 x不是y得到的   反向真值。 [6]

来自python3 docs:

  

运算符是和不是对象标识的测试:x是y是真的   当且仅当x和y是同一个对象时。对象标识是   使用id()函数确定。 x不是y得到逆   真相价值。 [4]

所以基本上理解你在repl控制台上运行的那些测试的关键是使用 相应地id()功能,这里有一个例子,可以告诉你窗帘后面发生了什么:

>>> a=256
>>> id(a);id(256);a is 256
2012996640
2012996640
True
>>> a=257
>>> id(a);id(257);a is 257
36163472
36162032
False
>>> a=257;id(a);id(257);a is 257
36162496
36162496
True
>>> a=12345;id(a);id(12345);a is 12345
36162240
36162240
True

也就是说,通常使用dis.disdis.disco来了解使用source codeyoutube-dl的窗帘背后发生的事情的好方法,让我们来看看看看这个片段的样子:

import dis
import textwrap

dis.disco(compile(textwrap.dedent("""\
    a=256
    a is 256
    a=257
    a is 257
    a=257;a is 257
    a=12345;a is 12345\
"""), '', 'exec'))
输出将是:

  1           0 LOAD_CONST               0 (256)
              2 STORE_NAME               0 (a)

  2           4 LOAD_NAME                0 (a)
              6 LOAD_CONST               0 (256)
              8 COMPARE_OP               8 (is)
             10 POP_TOP

  3          12 LOAD_CONST               1 (257)
             14 STORE_NAME               0 (a)

  4          16 LOAD_NAME                0 (a)
             18 LOAD_CONST               1 (257)
             20 COMPARE_OP               8 (is)
             22 POP_TOP

  5          24 LOAD_CONST               1 (257)
             26 STORE_NAME               0 (a)
             28 LOAD_NAME                0 (a)
             30 LOAD_CONST               1 (257)
             32 COMPARE_OP               8 (is)
             34 POP_TOP

  6          36 LOAD_CONST               2 (12345)
             38 STORE_NAME               0 (a)
             40 LOAD_NAME                0 (a)
             42 LOAD_CONST               2 (12345)
             44 COMPARE_OP               8 (is)
             46 POP_TOP
             48 LOAD_CONST               3 (None)
             50 RETURN_VALUE

正如我们在这种情况下可以看到的那样,asm输出并没有告诉我们,我们可以看到,第3-4行基本上是"相同"说明而不是第5行。因此,我的建议是再次巧妙地使用id(),以便您知道is将会比较什么。如果您想确切知道cpython正在进行的优化类型,我担心您需要挖掘其turning on the --enable-gnutls option

答案 3 :(得分:0)

一般而言,-5到256范围之外的数字不一定会将优化应用于该范围内的数字。但是,Python可以根据需要自由应用其他优化。在您的事业中,您会发现在一行中多次使用的相同文字值存储在单个内存位置,无论它在该行上使用了多少次。以下是此行为的其他一些示例:

>>> s = 'a'; s is 'a'
True
>>> s = 'asdfghjklzxcvbnmsdhasjkdhskdja'; s is 'asdfghjklzxcvbnmsdhasjkdhskdja'
True
>>> x = 3.14159; x is 3.14159
True
>>> t = 'a' + 'b'; t is 'a' + 'b'
True
>>>