我正在学习 python 中的内存管理。最近我正在探索可变对象和不可变对象的内存地址差异。起初我得出的结论是,出于优化目的,相同的对象也是不可变的,结果只有一个内存地址。可变对象总是接收新的内存地址。
但是在询问了一些人并探索了一些代码片段之后,我得到了我无法理解的结果。请看下面的代码:
x = (1, 2) # 0x111
y = (1, 2) # 0x111
print(hex(id(x)))
print(hex(id(y)))
a = tuple([1, 2]) # 0x222
b = tuple((1, 2)) # 0x111
c = tuple(range(1, 2)) # 0x333
d = tuple(_ for _ in x) # 0x444
print(hex(id(a)))
print(hex(id(b)))
print(hex(id(c)))
print(hex(id(d)))
print(x, y, a, b, c, d) # (1, 2) (1, 2) (1, 2) (1, 2) (1, 2) (1, 2)
根据这段代码,我希望它们都应该有相同的内存地址。
为什么那些相同的元组之间的内存地址不同?
编辑:我还应该声明我想在 IDE 中讨论这个过程,而不是 python 控制台。
当您调用
tuple([1, 2])
(或任何非空操作;毕竟,调用 tuple((1, 2))
只能返回相同的元组)时,Python 不会只查看它之前构造的所有元组,所以它可以返回同样的东西。 (如果你想要那样的东西,你会使用@functools.cache
)。这就是为什么 tuple([1, 2])
和所有其他人返回具有新 ID 的新对象的原因。
此外,当您使用 Python(好吧,CPython,因为这是一个实现细节)加载模块(例如从磁盘)时,它会被编译成字节码,并且该过程确实包括一两个优化。
如果我们在您的代码上调用
dis
反汇编器,我们看到它以开头
$ python3.10 -m dis so75512629.py
1 0 LOAD_CONST 0 ((1, 2))
2 STORE_NAME 0 (x)
2 4 LOAD_CONST 0 ((1, 2))
6 STORE_NAME 1 (y)
即同样的第0个const保存成两个不同的名字,后面就
8 52 LOAD_NAME 5 (tuple)
54 LOAD_CONST 0 ((1, 2))
56 CALL_FUNCTION 1
58 STORE_NAME 7 (b)
我们在同一个 const 上调用
tuple
(正如上面所讨论的,tuple(t)
对于元组 t
是一个空操作)。
在 REPL 会话中进入单独的套件时,这 不 成立,因为它们是单独编译的:
>>> a = (1, 2)
>>> b = (1, 2)
>>> id(a)
4318599488
>>> id(b)
4319249472
>>>
但是,优化确实适用于within单个编译套件:
>>> c = [(1, 2), (1, 2)]
>>> id(c[0])
4319257472
>>> id(c[1])
4319257472