从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
为什么?
从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
为什么?
你所看到的是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(我首选的IDE)中的交互式shell中执行时,不会导致对同一对象的两个引用。我不知道为什么这种特定情况与传统交互式shell中的情况不同,但不同的行为是我的错,因为我的代码依赖于特定于实现的行为。
来自python2文档:
运算符是和不测试对象标识:x是y是真的 当且仅当x和y是同一个对象时。 x不是y得到的 反向真值。 [6]
来自python3文档:
运算符是和不测试对象标识: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.dis 要么 dis.disco,让我们来看看这个代码片段的样子:
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正在做的优化类型,我担心你需要挖掘它的 源代码
经过各种版本的讨论和测试,可以得出最终结论。
Python将以块的形式解释和编译指令。根据所使用的语法,Python版本,操作系统,分发,可以实现不同的结果,具体取决于Python在一个块中采用的指令。
一般规则是:
(从 官方文件)
当前实现为所有实体保留了一个整数对象数组 -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
下面的部分返回False Python 3.6.3 | Anaconda自定义(64位)| (默认,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在“一个块”中采用什么都是非确定性的,并且可以根据它的编写方式,单线或不一样,以及所使用的版本,操作系统和分布而摇摆不定。
一般而言,-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
>>>