`is` 运算符对非缓存整数的行为异常

2025-01-09 08:47:00
admin
原创
94
摘要:问题描述:在使用 Python 解释器时,我偶然发现了有关is运算符的这个冲突情况:如果求值发生在函数中,则返回True,如果求值发生在函数外部,则返回False。>>> def func(): ... a = 1000 ... b = 1000 ... return...

问题描述:

在使用 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()`abint`func

为什么会这样?


注意:我知道身份(is)和相等(==)运算之间的区别,如理解 Python 的“is”运算符中所述。此外,我还知道 Python 对范围内的整数执行的缓存,如“is”运算符对整数的行为异常[-5, 256]中所述。

这里的情况并非如此,因为数字超出了该范围,我确实想评估身份而不是相等性。


解决方案 1:

总结:

正如参考手册所述:

块是一段作为一个单元执行的 Python 程序文本。以下是块:模块、函数体和类定义。
以交互方式输入的每个命令都是一个块。

这就是为什么在函数的情况下,有一个包含数字文字的
单个对象的单个代码块,因此会产生。1000`id(a) == id(b)`True

在第二种情况下,您有两个不同的代码对象,1000每个代码对象对于文字so都有自己不同的对象id(a) != id(b)

请注意,这种行为不仅仅体现在int文字上,您还可以使用例如float文字获得类似的结果(参见此处)。

当然,比较对象(除了明确的测试)应该始终使用相等运算符而不是来is None完成。== is

此处所述的所有内容均适用于最流行的 Python 实现 CPython。其他实现可能有所不同,因此在使用它们时不应做任何假设。


较长的答案:

为了获得更清晰的视图并进一步验证这种看似奇怪的行为,我们可以使用模块直接查看code每种情况的对象dis

对于该函数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

我们只对Constants函数的条目感兴趣func。在其中,我们可以看到我们有两个值None(始终存在)和1000。我们只有一个表示常量的 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看起来相同,但有一个根本区别:每个代码对象com1com2都有不同的 int 实例来表示文字1000。这就是为什么在这种情况下,当我们a is b通过co_consts参数执行时,我们实际上会得到:

>>> 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 的回答。


注意事项:

  • 链式语句将评估身份检查True

现在应该更清楚为什么以下内容的计算结果为True

 >>> a = 1000; b = 1000;
 >>> a is b

在这种情况下,通过将两个赋值命令链接在一起,我们告诉解释器将它们一起1000编译。与函数对象的情况一样,只会创建一个文字对象,并True在评估时产生一个值。

  • 在模块级别执行True再次产生:

如前所述,参考手册指出:

...以下是块:模块...

因此同样的前提适用:我们将有一个单一的代码对象(用于模块),因此,每个不同的文字都会存储单一的值。

  • 但对于可变对象则不然

这意味着,除非我们明确地初始化为同一个可变对象(例如用a = b = []),否则对象的标识永远不会相等,例如:

    a = []; b = []
    a is b  # always evaluates to False

再次,在文档中,有如下指定:

在 a = 1; b = 1 之后,a 和 b 可能会或可能不会引用具有值 1 的同一个对象,这取决于实现方式,但在 c = []; d = [] 之后,c 和 d 保证引用两个不同的、唯一的、新创建的空列表。

解决方案 2:

在交互式提示下,条目以单一模式编译,每次处理一个完整语句。编译器本身(在Python/compile.c中)跟踪名为u_consts的字典中的常量,该字典将常量对象映射到其索引。

在compiler_add_o()函数中,你会看到在添加新常量(并增加索引)之前,会检查字典以查看常量对象和索引是否已存在。如果存在,则重用它们。

简而言之,这意味着一条语句中的重复常量(例如函数定义中的常量)将被折叠为一个单例。相比之下,a = 1000b = 1000是两个单独的语句,因此不会发生折叠。

无论如何,这只是 CPython 实现细节(即语言不保证)。这就是为什么这里给出的参考资料是 C 源代码,而不是语言规范,后者对这个问题不作任何保证。

希望你喜欢这篇关于 CPython 底层工作原理的文章 :-)

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1590  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1361  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   18  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   18  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   19  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用