填充列表-python在做什么?

populating lists — what is python doing?

我想我已经解决了我自己的问题,但我正在寻求更好的理解为什么,或者是开明/直截了当。

我有一个列表,我称之为vec:

1
vec = [0.0, 0.0]

在读取数据时更改值。

为了比较当前值和以前的值,我有另一个列表,我称之为oldvec。如果我把oldvec定义为

1
oldvec = vec

然后,每次vec更改值时,它都会更改值,所以比较是无用的——它们总是相同的。

但是,如果我改为写

1
oldvec = [vv for vv in vec]

我没有这个问题——oldvec在vec改变的时候保持它的值,所以当前和以前的向量之间的比较是按我需要的方式工作的,也就是说,它实际上检测重复和不重复!…为什么?


你能看到引擎盖下面发生了什么的一种方法是使用id功能。这将显示对象的内存地址。内存地址是指对象存储在物理内存中的位置。

如果我们为这三个命令运行它,并查看不同的地址(这只是在我的计算机上;如果您自己运行它,您将获得不同的号码):

1
2
3
4
5
6
7
8
9
10
11
>>> vec = [0.0, 0.0]
>>> print id(vec)
4501729936

>>> oldvec1 = vec
>>> print id(oldvec1)
4501729936

>>> oldvec2 = [vv for vv in vec]
>>> print id(oldvec2)
4502046984

我们看到vecoldvec1指的是同一个地址,所以它们是同一对象的两个不同标签。在引擎盖下,python操作地址为4501729936的对象:变量名vecoldvec1只是方便我们使用的标签。它们不指"独特"的物体。

相比之下,oldvec2则完全不同。当python运行列表理解时,它不知道这会产生与以前相同的列表,因此它创建了该列表的新副本。

这是一张很快的脏照片来显示发生了什么。虽然红色blob和绿色blog恰好包含相同的信息,但它们是两个不同的blob。vecoldvec1都指向同一个红色斑点,因此对其中一个的任何操作都将影响基础的红色斑点,并反映在另一个斑点中。相比之下,oldvec2指向完全不同的绿色斑点,它恰好是红色斑点中信息的副本,但对绿色斑点的更改不会影响红色斑点。

enter image description here


将oldvec设置为vec实际上使oldvec指向vec。您没有创建新的列表,只是为它取了另一个名称。通过使用列表理解,您可以显式地创建列表的新副本,相当于vec.copy()。


您应该将python列表看作一个对象:在内存中的某个地方实例化的对象,其中有一个或多个指针存储其内存地址——也就是说,当您说[]时,您正在内存中的某个地方分配一些新的空间。所以,当您调用vec = [0.0, 0.0]时,它会在内存的某个地方创建一个新的列表,并且它的地址存储在vec变量中。所以,当您执行oldvec = vec操作时,您只需将地址从vec复制到oldvec

让我举例说明一下:例如,假设您的列表[0.0, 0.0]存储在地址0x0800处。当你说vec = [0.0, 0.0]时,vec变量现在接收0x0800。当你说oldvec = vec时,oldvec收到的是同一个0x0800。因此,当您访问oldvec的第一个元素时,实际上您访问的是vec指向的同一个列表。

现在,想想你的新品:oldvec = [vv for vv in vec]。当您执行[]操作时,它会在内存中的其他地方创建一个新的列表,对吗?正如for命令所说,这个列表由vec的元素填充。所以,它在内存中的其他地方创建了一个新的列表,存储0.00.0(如果我正确理解了您所解释的内容,稍后还会存储新元素)。这就是Python内部处理命令的方式。

希望有帮助。


在Python中,变量是"引用",这意味着可以有两个变量引用同一个对象。在您的第一个示例中,正在发生的事情是:同一个列表有两个名称。

如果您需要第二个实际列表,可以"复制"第一个列表。有关方法,请参见此处:如何克隆或复制列表?

请注意,这也适用于列表中的项目——如果它们是更复杂的对象,您可以选择对列表进行"深度复制",复制每个元素的每个单独部分,或者只复制引用"浅复制",这样您就有了一个包含对原始对象新引用的新列表。您需要为每个用例选择正确的方法。