猿问
下载APP

'is'运算符与非缓存整数意外地运行

'is'运算符与非缓存整数意外地运行

在玩Python解释器时,我偶然发现了关于is运算符的这个冲突的情况:

如果评估发生在它返回的函数中True,如果它在外部完成则返回False

>>> def func():...     a = 1000...     b = 1000...     return a is b...>>> a = 1000>>> b = 1000>>> a is b, func()(False, True)

由于is运营商评估id()的参与对象,这意味着ab指向同一个int函数内声明的时候实例func,但是,相反,它们指向一个不同的对象时,它的外面。

为什么会这样?


注意:我知道identity(is)和equal(==)操作之间的区别,如了解Python的“is”操作符中所述。另外,我也知道python正在对范围内的整数执行缓存,[-5, 256]“is”中所述,运算符与整数一起出现意外行为。 

这不是这里的情况,因为数字超出了该范围,我确实想要评估身份而不是平等。


45度呼吸
浏览 64回答 2
2回答

幕布斯7119047

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

慕盖茨9453107

在交互式提示符下,条目以单一模式编译,一次处理一个完整的语句。编译器本身(在Python / compile.c中)跟踪名为u_consts的字典中的常量,该常量将常量对象映射到其索引。在compiler_add_o()函数中,您会看到在添加新常量(并递增索引)之前,将检查dict以查看常量对象和索引是否已存在。如果是这样,它们将被重用。简而言之,这意味着一个语句中的重复常量(例如在函数定义中)被折叠成一个单例。相比之下,您a = 1000和b = 1000两个单独的陈述,因此不会发生折叠。FWIW,这只是一个CPython实现细节(即语言无法保证)。这就是为什么这里给出的引用是C源代码而不是语言规范,它不能保证主题。希望您喜欢CPython如何在引擎盖下工作的洞察力:-)
打开App,查看更多内容
随时随地看视频慕课网APP
我要回答