理解python中的列表切片赋值

时间:2013-07-21 23:27:42

标签: python

我正在做一个简单的练习来理解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]

3 个答案:

答案 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)L1L3内的列表已更改。

相比之下,当不需要浅层复制时,您可以deepcopy

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