理解python中的列表片分配

理解python中的列表片分配,python,Python,我在做一个简单的练习来理解列表分配的机制。如果我将一个列表L1分配给L2,然后将一个元素附加到L1,L1和L2都会改变。但是,如果我将列表L3分配给L2的子片,然后将元素附加到L3,则L3中的更改将与L2断开连接。这是如何实现的?L3现在实际上指向一个不同的位置,其中包含列表的子片,而L1和L2指向同一位置。是这样吗 >>> L1 = [] >>> L2 =[1,2,3] >>> L1 = L2 >>> L1.append(

我在做一个简单的练习来理解列表分配的机制。如果我将一个列表L1分配给L2,然后将一个元素附加到L1,L1和L2都会改变。但是,如果我将列表L3分配给L2的子片,然后将元素附加到L3,则L3中的更改将与L2断开连接。这是如何实现的?L3现在实际上指向一个不同的位置,其中包含列表的子片,而L1和L2指向同一位置。是这样吗

>>> L1 = []
>>> L2 =[1,2,3]
>>> L1 = L2
>>> L1.append(4)
>>> L1
[1, 2, 3, 4]
>>> L2
[1, 2, 3, 4]
>>> L3 =L2[:2]
>>> L3
[1, 2]
>>> L3.append(5)
>>> L3
[1, 2, 5]
>>> L2
[1, 2, 3, 4]

答案很简单:分配分配一个引用。这就是为什么对
L1
的更改在
L2
中可见–它们是相同的对象

但是,切片会创建范围的(浅)副本。因此,对
L3
的更改与
L2
断开连接

事实上,为了创建序列的副本,由于不能使用直接赋值,因此可以使用切片:

>>> L4 = L2[:]
>>> L4.append(5)
>>> L2
[1, 2, 3, 4]

…然而,这通常是通过构造函数来完成的,即
L4=list(L2)
要添加到上述答案中,当您有一个可变对象的列表而不是整数或字符串时,副本的性质变得很重要。即使在切片时,列表的元素仍然指向相同的对象

>>> a = { 'a': 0 }
>>> b = { 'b' : 0 }
>>> c = { 'c' : 0 }
>>> l = [ a, b, c ]
>>> m = l[ : 2] 
ml包含对相同事物的引用

>>> m
[{'a': 0}, {'b': 0}]
>>> m[0]['a'] = 1
>>> m
[{'a': 1}, {'b': 0}]
>>> l
[{'a': 1}, {'b': 0}, {'c': 0}]
然而,ml是不同的东西

>>> m[0] = {'d': 1}
>>> m
[{'d': 1}, {'b': 0}]
>>> l
[{'a': 1}, {'b': 0}, {'c': 0}] 
(我在例子上浪费了太多时间,@Konrad Rudolph抢先找到了答案,我基本上和他说了同样的话)

Python在后台执行相当于C指针赋值的操作,L1在内存中保存列表的地址,L2只是复制了相同的地址

您只需使用
is
操作符即可签入代码

>>> L1 is L2
True
但是,当使用切片时,必须创建一个新的列表,因为它与原始列表不同,但是python作为一个鬼鬼祟祟的混蛋,它不是复制所有内容,而是复制对其中对象的引用

这里有一个更详细的例子:

>>> L1 = [1, [2, 3], 4]
>>> L2 = L1
>>> L2
[1, [2, 3], 4]
>>> L1[1] is L2[1]
True
好的,
L1
保存了一个列表,里面有一个列表,您可以用字典、字符串或任何其他python对象来替换它。请注意,对于列表中的列表,
运算符返回的
True

然后,如果执行切片技巧:

>>> L3 = L1[:2]
>>> L3
[1, [2, 3]]
>>> L3 is L1
False
>>> L3[1] is L2[1]
True
外部列表已更改,但内部对象保持不变,这称为浅层复制。事实上,同样的规则一直适用,如果我们在列表中添加一个新项目:

>>> L3[1].append(9)
>>> L3
[1, [2, 3, 9]]
>>> L1
[1, [2, 3, 9], 4]
(注意添加的9)
L1
L3
中的列表都已更改

相反,当不需要浅拷贝时,您可以执行
deepcopy

>>> from copy import deepcopy
>>> L3 = deepcopy(L1)
>>> L3
[1, [2, 3, 9], 4]
>>> L3 is L1
False

从列表创建一个
slice
会创建一个浅拷贝…@John为什么你对我说浅拷贝L4=L2[:]看起来像一个深拷贝,因为L4现在指向一个包含L2[:]的新内存位置深拷贝会创建
L2
元素的深拷贝。你认为浅拷贝是深拷贝,因为你认为是浅拷贝的东西-直接任务-实际上根本不是拷贝。这是C++语言之间的一个根本区别,其中变量是放置事物和赋值创建副本的地方,而语言是Python,其中变量是名称,而赋值只是移动NUMGATAG。USER357112,但是当L4=L2[]之后,我将元素附加到L4时,L2和L4是如何断开的?不是因为L4和L2现在指向不同的内存位置,尽管列表元素具有相同的值吗?我猜这是因为List是一个包含其他对象的复合对象,否则浅层和深层拷贝之间的区别不存在于Python中,所以基本上L4= L2[]:C++中的浅拷贝KrrAP是一个类似的对象。要对一个普通类实现同样的功能,我应该怎么做?对我来说,L4=L2[:]似乎是一个深度副本,因为L4现在指向一个新的内存位置,其中包含L2[:]@vkaul11,这是一个浅层副本。“深度复制”意味着列表中的所有元素也会被复制,但事实并非如此——尝试在列表中放置一个可修改的元素,复制列表并修改对象。它将在列表的所有实例中被修改,因为列表仅存储对对象的引用,而不是对象本身。