我正在做一个简单的练习来理解List赋值的机制。如果我将列表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]
答案 0 :(得分:2)
答案很简单:作业分配参考。这就是L1
中对L2
的更改可见的原因 - 它们是同一个对象。
但是,切片会创建范围的(浅)副本。因此,L3
的更改与L2
无关。
实际上,为了创建序列的副本,由于您不能使用直接赋值,您可以改为使用切片:
>>> L4 = L2[:]
>>> L4.append(5)
>>> L2
[1, 2, 3, 4]
...但是,这通常是通过构造函数完成的,即L4 = list(L2)
。
答案 1 :(得分:0)
要添加上面的答案,当您拥有可变对象列表而不是整数或字符串时,副本的性质就变得很重要。即使切片,列表的元素仍然指向相同的对象。
>>> a = { 'a': 0 }
>>> b = { 'b' : 0 }
>>> c = { 'c' : 0 }
>>> l = [ a, b, c ]
>>> m = l[ : 2]
m 和 l 包含对相同内容的引用。
>>> m
[{'a': 0}, {'b': 0}]
>>> m[0]['a'] = 1
>>> m
[{'a': 1}, {'b': 0}]
>>> l
[{'a': 1}, {'b': 0}, {'c': 0}]
然而, m 和 l 是不同的事情。
>>> m[0] = {'d': 1}
>>> m
[{'d': 1}, {'b': 0}]
>>> l
[{'a': 1}, {'b': 0}, {'c': 0}]
答案 2 :(得分: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对象替换它。请注意,is
运算符为列表中的列表返回了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