我试图找出python仅实例化一次的整数(似乎为-6到256),并且在此过程中偶然发现某些字符串行为,我看不到模式。有时,以不同方式创建的相等字符串共享相同的字符串id,有时不是。这段代码:
A = "10000" B = "10000" C = "100" + "00" D = "%i"%10000 E = str(10000) F = str(10000) G = str(100) + "00" H = "0".join(("10","00")) for obj in (A,B,C,D,E,F,G,H): print obj, id(obj), obj is A
印刷品:
10000 4959776是 10000 4959776是 10000 4959776是 10000 4959776是 10000 4959456错误 10000 4959488错误 10000 4959520错误 10000 4959680错误
我什至没有看到这种模式-除了前四个没有显式的函数调用的事实之外-但肯定不能这样,因为+例如C语言中的“ ”表示对 add 的函数调用。我特别不明白为什么C和G不同,因为这意味着加法的组成部分的ID比结果更重要。
+
那么,AD受到什么特殊对待,使它们成为同一个实例呢?
在语言规范方面,对于不可变类型的任何实例,完全允许任何兼容的Python编译器和运行时创建新实例,或查找等于所需值的相同类型的现有实例,并使用新引用来同一实例。这意味着is在不可变项之间使用或按ID进行比较总是不正确的,任何次要发行版都可能对此进行调整或更改策略以增强优化。
is
在实现方面,权衡非常明确:尝试重用现有实例可能意味着花费(也许浪费了)尝试找到这样的实例的时间,但是如果尝试成功,则会节省一些内存(以及分配时间)然后释放用于保存新实例所需的内存位)。
如何解决这些实现方面的折衷方案并不完全清楚-如果您可以找出启发式方法,表明有可能找到合适的现有实例,并且搜索(即使失败)也会很快,那么您可能想要尝试搜索- -启发式提示时重用,否则跳过。
在您的观察中,您似乎发现了一种特殊的点释放实现,该实现在完全安全,快速且简单的情况下执行了一些窥孔优化,因此A到D的分配都归结为与A完全相同(但E到F不这样做,因为它们涉及命名函数或方法,优化器的作者可能合理地认为,假设语义不是100%安全的- 如果这样做的话,则是低投资回报率的-因此未对窥视孔进行优化)。
因此,A到D重用相同的实例归结为A和B这样做(因为C和D被窥视孔优化为完全相同的构造)。
反过来,这种重用清楚地表明了编译器策略/优化器试探法,即将同一函数的本地名称空间中不变类型的相同文字常量折叠为对函数中一个实例的引用.func_code.co_consts(使用当前CPython的术语表示函数和代码的属性)对象)-合理的策略和试探法,因为在一个函数中重复使用相同的不变常量文字有些频繁,并且价格仅需支付一次(在编译时),而优势却可以多次获得(每次函数运行时,可能在循环内等)。
.func_code.co_consts
(碰巧的是,鉴于这些明确的权衡取舍,这些特定的策略和启发式方法已在CPython的所有最新版本中普遍存在,并且我相信IronPython,Jython和PyPy也是如此;-)。
如果您打算为Python本身或类似语言编写编译器,运行时环境,窥孔优化器等,这是一个值得研究的有趣话题。我猜想对内部结构进行深入研究(当然,最好是对许多不同的正确实现进行研究,以免专注于特定的怪癖——Python目前至少拥有4种独立于生产的实现,这是一件好事,更不用说了每个版本都有多个版本!)还可以间接地帮助一个更好的Python程序员- 但是,重点放在语言本身所 保证 的内容上尤其重要,这要比单独的实现中常见的要少一些,因为“正好发生”的部分 __(如果语言规范如此),则在下一个版本或另一个实现的下一个发行版时,您可能会完全改变,并且,如果您的生产代码错误地依赖于此类详细信息,则可能会导致令人讨厌的意外;-)。 另外-几乎不必依赖于这样的可变实现细节而不是依赖于语言规定的行为(除非您要编写诸如优化器,调试器,分析器之类的代码;- )。