Python,变量存储在内存中

6
a=[1234,1234] #list

a      
[1234, 1234] 

id(a[0])      
38032480

id(a[1])      
38032480

b=1234 #b is a variable of integer type

id(b)      
38032384

为什么在 Python 中,id(b) 与 id(a[0]) 和 id(a[1]) 不同呢?涉及 IT 技术相关内容。

在Python中,值1234必须只有一个内存位置。为什么id(b)显示不同的内存位置?请回答这个问题。 - Aman Tyagi
2
这并不是一个答案,而是关于字符串常量池的一些见解:http://guilload.com/python-string-interning/。似乎列表构造器默认会进行一些常量池优化。 - hiro protagonist
1
@hiroprotagonist 我认为是 int 构造函数在进行内部化,而不是 list - kennytm
但是在Python中,如果我写a=1; b=1;,两者的id(a) == id(b),但现在它给出了不同的输出。 - Aman Tyagi
@aman-tyagi:是的,如果id相同,你的变量指向同一“内存位置”,如果id不同,则不是。 - hiro protagonist
显示剩余5条评论
4个回答

2
当CPython REPL执行一行代码时,它会:
  1. 解析并编译成字节码的代码对象,然后
  2. 执行字节码。
可以通过dis模块检查编译结果:
>>> dis.dis('a = [1234, 1234, 5678, 90123, 5678, 4321]')
  1           0 LOAD_CONST               0 (1234)
              2 LOAD_CONST               0 (1234)
              4 LOAD_CONST               1 (5678)
              6 LOAD_CONST               2 (90123)
              8 LOAD_CONST               1 (5678)
             10 LOAD_CONST               3 (4321)
             12 BUILD_LIST               6
             14 STORE_NAME               0 (a)
             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

请注意,所有的1234都加载了"LOAD_CONST 0",而所有的5678都加载了"LOAD_CONST 1"。它们指的是与代码对象相关联的常量表。在这里,该表为(1234, 5678, 90123, 4321, None)
编译器知道代码对象中所有的1234副本都是相同的,因此只会为它们分配一个对象。
因此,正如 OP 观察到的那样,a[0]a[1]确实指向同一个对象:该行代码的代码对象的常量表中的相同常量。
当你执行b = 1234时,这将再次被编译和执行,与之前的行无关,因此将分配一个不同的对象。
(您可以阅读http://akaptur.com/blog/categories/python-internals/,了解有关如何解释代码对象的简要介绍)
在REPL之外,当您执行一个*.py文件时,每个函数都会被编译成单独的代码对象,因此当我们运行以下命令时:
a = [1234, 1234]
b = 1234
print(id(a[0]), id(a[1]))
print(id(b))

a = (lambda: [1234, 1234])()
b = (lambda: 1234)()
print(id(a[0]), id(a[1]))
print(id(b))

我们可能会看到类似这样的东西:
4415536880 4415536880
4415536880
4415536912 4415536912
4415537104

前三个数字共享同一地址4415536880,它们属于"__main__"代码对象的常量。然后,a[0]和a[1]具有第一个lambda的地址4415536912。b具有第二个lambda的地址4415537104。
请注意,这个结果仅适用于CPython。其他实现在分配常量方面有不同的策略。例如,在PyPy中运行上述代码会得到以下结果:
19745 19745
19745
19745 19745
19745

所以您认为这与list构造函数有关,而不是int构造函数?(不想聪明地回答;只是好奇)无论如何+1!感谢您的回答。 - hiro protagonist
@hiroprotagonist 实际上都不是,这与编译器有关。当列表构造函数被调用时,它已经看到了两个相同的对象。 - kennytm
哦,好的!抱歉,从反汇编的代码中应该很明显...谢谢! - hiro protagonist

1
没有规则或保证表明id(a [0])应该等于id(a [1]),因此问题本身是无意义的。你应该问的问题是为什么id(a [0])和id(a [1])实际上是相同的。
如果你执行a.append(1234),然后执行id(a[2]),你可能会得到相同的id,也可能不会。正如@hiro protagonist所指出的那样,这些只是你不应该依赖的内部优化。

1
一个Python列表与C数组非常不同。
C数组只是一块连续的内存,因此它的第一个(0号)元素的地址就是数组本身的地址,根据定义。在C中,数组访问只是指针算术运算,而[]符号只是对指针算术运算的薄薄的语法糖。表达式int x[]只是int * x的另一种形式。
为了举例说明,在Python中假设id(x)是X的“内存地址”,就像C中的*x一样。(这并不适用于所有Python实现,甚至不保证在CPython中。它只是一个唯一的数字。)
在C语言中,int只是一个与架构相关的字节数,因此对于int x = 1,表达式*x指向这些字节。在Python中,所有东西都是对象,包括数字。这就是为什么id(1)引用了一个描述数字1int类型对象。您可以调用它的方法:(1).__str__()将返回一个字符串'1'
因此,当您有x = [1, 2, 3]时,id(x)是指向具有三个元素的list对象的"指针"。list对象本身相当复杂。但是x[0]不是组成整数值1的字节;它在内部是对数字1的int对象的引用。因此,id(x[0])是指向该对象的"指针"。
以C语言的术语来看,数组的元素可以被视为指向其中存储的对象的指针,而不是对象本身。

由于没有必要有两个表示同一个数字1的对象,id(1) 在 Python 解释器运行期间始终相同。下面是一幅图示:

x = [1, 2, 3]
y = [1, 100, 1000]

assert id(x) != id(y)  # obviously
assert id(x[0]) == id(y[0]) == id(1) # yes, the same int object

CPython实际上为一些最常用的小数字预分配对象(请参见此处的评论)。对于较大的数字,情况并非如此,这可能会导致两个“副本”具有不同的id()值(请参见此处)。

1
请注意:id()实际上给出变量或文字值的id。对于程序中使用的每个文字/值(即使在id()内部),id()都会返回(尝试返回)该程序生命周期内该文字/变量的唯一标识符。这可以用于:
  • 用户:检查两个对象/变量是否相同,例如:a is b
  • Python:优化内存,即避免不需要的重复内存占用
就您的情况而言,甚至不能保证a[0]和a[1]会给出相同的id,尽管它们的值可能相同。这取决于python程序生命周期中文字/变量的创建顺序/时间轴,并由python在内部处理。

案例1:

Type "help", "copyright", "credits" or "license" for more information.
>>> a=[1234,1234] 
>>> id(a[0])
52687424
>>> id(a[1])
52687424

案例2(请注意,在该案例结束时,a [0]和a [1]具有相同的值但不同的标识符):

Type "help", "copyright", "credits" or "license" for more information.
>>> a=[1,1234]
>>> id(1)
1776174736
>>> id(1234)
14611088
>>> id(a[0])
1776174736
>>> id(a[1])
14611008
>>> a[0]=1234
>>> id(1234)
14611104
>>> id(a[0])
14611152
>>> id(a[1])
14611008
>>>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接