小能豆

Python 元组的内部结构

py

>>> a=1
>>> b=1
>>> id(a)
140472563599848
>>> id(b)
140472563599848
>>> x=()
>>> y=()
>>> id(x)
4298207312
>>> id(y)
4298207312
>>> x1=(1)
>>> x2=(1)
>>> id(x1)
140472563599848
>>> id(x2)
140472563599848

直到此时我还在想只会有一个不可变对象的副本,并且它将被所有变量共享(指向)。

但是当我尝试以下步骤时我明白我错了。

>>> x1=(1,5)
>>> y1=(1,5)
>>> id(x1)
4299267248
>>> id(y1)
4299267320

有人可以向我解释一下其内部原理吗?


阅读 13

收藏
2024-10-28

共3个答案

小能豆

>>> x1=(1)
>>> x2=(1)

实际上与

>>> x1=1
>>> x2=1

在 Python 中,较小的数字会被内部缓存。因此它们不会在内存中多次创建。这就是为什么idx1x2到目前为止都是相同的。

单元素元组末尾应该有一个逗号,如下所示

>>> x1=(1,)
>>> x2=(1,)

执行此操作时,将构造两个新的元组,每个元组中只有一个元素。即使元组中的元素相同,它们也是不同的元组。这就是为什么它们都有不同的ids。

让我们采取你的最后一个例子并反汇编代码。

compiled_code = compile("x1 = (1, 5); y1 = (1, 5)", "string", "exec")

现在,

import dis
dis.dis(compiled_code)

会产生类似这样的结果

  1           0 LOAD_CONST               3 ((1, 5))
              3 STORE_NAME               0 (x1)
              6 LOAD_CONST               4 ((1, 5))
              9 STORE_NAME               1 (y1)
             12 LOAD_CONST               2 (None)
             15 RETURN_VALUE

它加载索引为 的常量值,3(1, 5),然后将其存储在 中x1。同样,它加载索引处的另一个常量值,4并将其存储在 中y1。如果我们查看代码对象中的常量列表,

print(compiled_code.co_consts)

将给予

(1, 5, None, (1, 5), (1, 5))

3位置和处的元素4是我们在实际代码中创建的元组。因此,Python 不会为每个不可变对象只创建一个实例,始终如此。这是一个实现细节,我们不必太担心。

注意:如果你希望只有一个不可变对象的实例,你可以手动这样做

x1 = (1, 5)
x2 = x1

现在,x2和都x1将引用同一个元组对象。

2024-10-28
小能豆

我想说的是,这种行为实际上已经发生了变化(从 Python 3.9 开始,尽管我不确定具体在哪个版本发生了变化)。现在看来,Python 编译器试图重用“一起编译”的代码的元组(不确定它是如何工作的),因此如果您x = (1, 5); y = (1, 5)在交互式 shell 中输入(一行),x is y则实际上为真。但是,如果您在交互式 shell 中输入x = (1, 5)和y = (1, 5)两行,那么它们仍然会有不同的 ID(与小整数和字符串不同)。

2024-10-28
小能豆

在我的 3.10 中,compiled_code.co_consts是((1, 5), None)- 元组被驻留。事实上,如果你在 shell 中的函数中或 .py 文件中的任何地方执行此操作x1=(1,),x2=(1,)它将被驻留为单个元组。我认为这只是强调了这只是一个实现细节,并且可以随时更改

2024-10-28