tl;博士:
正如reference manual 所说:
块是作为一个单元执行的一段 Python 程序文本。
以下是块:模块、函数体和类定义。
以交互方式键入的每个命令都是一个块。
这就是为什么在函数的情况下,您有一个 single 代码块,其中包含一个用于数字文字的 single 对象
1000,所以id(a) == id(b) 将产生True。
在第二种情况下,您有两个不同的代码对象,每个对象都有自己不同的对象,用于文字1000 所以id(a) != id(b)。
请注意,这种行为不会仅在 int 文字中体现,您将获得类似的结果,例如 float 文字(请参阅 here)。
当然,比较对象(显式 is None 测试除外)应始终使用相等运算符 == 和 not is。
这里所说的一切都适用于最流行的 Python 实现,CPython。其他实现可能会有所不同,因此在使用它们时不应做出任何假设。
更长的答案:
为了获得更清晰的视图并进一步验证这种看似奇怪的行为,我们可以使用dis模块直接查看code对象中的每一种情况。
对于函数func:
与所有其他属性一样,函数对象还具有__code__ 属性,可让您查看该函数的已编译字节码。使用dis.code_info,我们可以获得给定函数的代码对象中所有存储属性的漂亮视图:
>>> print(dis.code_info(func))
Name: func
Filename: <stdin>
Argument count: 0
Kw-only arguments: 0
Number of locals: 2
Stack size: 2
Flags: OPTIMIZED, NEWLOCALS, NOFREE
Constants:
0: None
1: 1000
Variable names:
0: a
1: b
我们只对函数func 的Constants 条目感兴趣。在其中,我们可以看到我们有两个值,None(始终存在)和1000。我们只有一个 single int 实例来表示常量 1000。这是在调用函数时将分配给 a 和 b 的值。
通过func.__code__.co_consts[1] 访问该值很容易,因此,在函数中查看我们的a is b 评估的另一种方法如下:
>>> id(func.__code__.co_consts[1]) == id(func.__code__.co_consts[1])
当然,这将评估为True,因为我们指的是同一个对象。
对于每个交互式命令:
如前所述,每个交互式命令都被解释为单个代码块:独立解析、编译和评估。
我们可以通过compile 内置函数获取每个命令的代码对象:
>>> com1 = compile("a=1000", filename="", mode="single")
>>> com2 = compile("b=1000", filename="", mode="single")
对于每个赋值语句,我们都会得到一个类似的代码对象,如下所示:
>>> print(dis.code_info(com1))
Name: <module>
Filename:
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
Stack size: 1
Flags: NOFREE
Constants:
0: 1000
1: None
Names:
0: a
com2 的相同命令看起来相同,但 有一个根本区别:每个代码对象 com1 和 com2 都有不同的 int 实例来表示文字 1000。这就是为什么在这种情况下,当我们通过 co_consts 参数执行 a is b 时,我们实际上得到:
>>> id(com1.co_consts[0]) == id(com2.co_consts[0])
False
这与我们实际得到的一致。
不同的代码对象,不同的内容。
注意:我有点好奇这到底是如何在源代码中发生的,在深入研究之后,我相信我终于找到了。
在编译阶段,co_consts 属性由字典对象表示。在compile.c我们实际上可以看到初始化:
/* snippet for brevity */
u->u_lineno = 0;
u->u_col_offset = 0;
u->u_lineno_set = 0;
u->u_consts = PyDict_New();
/* snippet for brevity */
在编译期间会检查已经存在的常量。请参阅@Raymond Hettinger's answer below 了解更多信息。
注意事项:
-
链式语句将评估为True 的身份检查
现在应该更清楚为什么下面的结果是True:
>>> a = 1000; b = 1000;
>>> a is b
在这种情况下,通过将两个赋值命令链接在一起,我们告诉解释器将这些一起编译。与函数对象的情况一样,只会为文字 1000 创建一个对象,从而在评估时产生 True 值。
-
在模块级别执行再次产生True:
如前所述,参考手册指出:
... 以下是块:一个模块 ...
所以同样的前提也适用:我们将有一个单独的代码对象(用于模块),因此,每个不同的文字都存储了一个值。
-
同样的不适用于可变对象:
意思是除非我们显式初始化为同一个可变对象(例如a = b = []),否则对象的身份永远不会相等,例如:
a = []; b = []
a is b # always evaluates to False
同样,在the documentation 中,这是指定的:
在 a = 1 之后; b = 1,a 和 b 可能会或可能不会引用具有值 1 的同一对象,具体取决于实现,但在 c = []; 之后d = [], c 和 d 保证引用两个不同的、唯一的、新创建的空列表。