Ami*_*ahi 3 python cpython immutability python-internals
据我所知,cpython 实现为某些相同的值保留相同的对象以节省内存。例如,当我创建 2 个带有 value 的字符串时hello,cpython 不会创建 2 个不同的字符串PyObject:
>>> s1 = 'hello'
>>> s2 = 'hello'
>>> s1 is s2
True
Run Code Online (Sandbox Code Playgroud)
我听说过名字string interning。当我尝试用其他 python 类型检查它时,我发现几乎所有可哈希(不可变)类型都是相同的:
>>> int() is int()
True
>>> str() is str()
True
>>> frozenset() is frozenset()
True
>>> bool() is bool()
True
Run Code Online (Sandbox Code Playgroud)
几乎所有可变类型都是相反的(cpythonPyObject甚至为相同的值创建一个新的类型):
>>> list() is list()
False
>>> set() is set()
False
>>> dict() is dict()
False
Run Code Online (Sandbox Code Playgroud)
我认为这是因为我们可以对PyObject不可变对象使用相同的方法而不会出现任何问题。
当我看到该float类型的行为与其他不可变类型不同时,我的问题就出现了:
>>> float() is float()
False
Run Code Online (Sandbox Code Playgroud)
为什么不一样呢?
可变对象总是创建一个新对象,否则数据将被共享。这里没有太多需要解释的,就好像您将一个项目附加到一个空列表中一样,您不希望所有空列表都有该项目。
不可变对象的行为方式完全不同:
字符串被拘留。如果它们小于 20 个字母数字字符,并且是静态的(代码、函数名称等中的常量),它们将被缓存并通过为其保留的特殊映射进行访问。它是为了节省内存,但更重要的是用于进行更快的比较。Python 在底层使用了大量需要字符串比较的字典访问操作。能够通过比较内存地址而不是实际值来比较属性或函数名称等两个字符串,这是一个显着的运行时改进。
布尔值只是返回同一个对象。考虑到只有 2 个可用,一次又一次地创建它们是没有意义的。
默认情况下,小整数(从 -5 到 256)也会被缓存。它们经常使用,几乎无处不在。每次整数在该范围内时,CPython 都会返回相同的对象。
然而,浮点数不会被缓存。与整数不同的是,数字 0-10 非常常见,但1.0不能保证比2.0或更常用0.1。这就是为什么float()只返回一个新的浮点数。我们可以优化空的float(),并且我们可以检查速度优势,但它可能不会产生如此大的差异。
当 时,混乱开始出现float(0.0) is float(0.0)。Python 内置了许多优化:
首先,const 被保存在每个函数的代码对象中。0.0 is 0.0只是指同一个对象。这是编译时优化。
其次,float(0.0)获取0.0对象,并且由于它是一个 float (不可变),因此它只是返回它。如果它已经是浮动对象,则无需创建新对象。
最后,1.0 + 1.0 is 2.0也会起作用。原因是它1.0 + 1.0是在编译时计算的,然后引用同一个2.0对象:
def test():
return 1.0 + 1.0 is 2.0
dis.dis(test)
2 0 LOAD_CONST 1 (2.0)
2 LOAD_CONST 1 (2.0)
4 IS_OP 0
6 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,没有加法操作。该函数的编译结果指向完全相同的常量对象。
因此,虽然没有特定于浮点的优化,但有 3 种不同的通用优化正在发挥作用。它们的总和最终决定它是否是同一个对象。