Python列表切片语法被无明显原因地使用

38

我偶尔会在Python代码中看到使用列表切片语法,就像这样:

newList = oldList[:]

这肯定就跟这个一样:

newList = oldList

还有什么我没注意到的吗?


1
我在我的博客上做了一个小示例:http://martin-thoma.blogspot.com/2011/03/understanding-python-lists.html - Martin Thoma
2
@Abgan,aList[:] 为什么不可靠? - jonrsharpe
5个回答

53

[:] 浅复制列表,复制包含对原始列表成员的引用的列表结构。这意味着对副本的操作不会影响原始结构。但是,如果您对列表成员进行了某些操作,则两个列表仍然引用它们,因此如果通过原始访问成员,则更新将显示出来。

深复制将复制所有列表成员。

下面的代码片段展示了浅复制的实现。

# ================================================================
# === ShallowCopy.py =============================================
# ================================================================
#
class Foo:
    def __init__(self, data):
        self._data = data

aa = Foo ('aaa')
bb = Foo ('bbb')

# The initial list has two elements containing 'aaa' and 'bbb'
OldList = [aa,bb]
print OldList[0]._data

# The shallow copy makes a new list pointing to the old elements
NewList = OldList[:]
print NewList[0]._data

# Updating one of the elements through the new list sees the
# change reflected when you access that element through the
# old list.
NewList[0]._data = 'xxx'
print OldList[0]._data

# Updating the new list to point to something new is not reflected
# in the old list.
NewList[0] = Foo ('ccc')
print NewList[0]._data
print OldList[0]._data

在Python shell中运行它会得到以下记录。我们可以看到列表是由旧对象的副本制成的。其中一个对象可以通过旧列表引用更新其状态,并且当通过旧列表访问该对象时,可以看到更新。最后,更改新列表中的引用不会反映在旧列表中,因为新列表现在引用的是另一个对象。
>>> # ================================================================
... # === ShallowCopy.py =============================================
... # ================================================================
... #
... class Foo:
...     def __init__(self, data):
...         self._data = data
...
>>> aa = Foo ('aaa')
>>> bb = Foo ('bbb')
>>>
>>> # The initial list has two elements containing 'aaa' and 'bbb'
... OldList = [aa,bb]
>>> print OldList[0]._data
aaa
>>>
>>> # The shallow copy makes a new list pointing to the old elements
... NewList = OldList[:]
>>> print NewList[0]._data
aaa
>>>
>>> # Updating one of the elements through the new list sees the
... # change reflected when you access that element through the
... # old list.
... NewList[0]._data = 'xxx'
>>> print OldList[0]._data
xxx
>>>
>>> # Updating the new list to point to something new is not reflected
... # in the old list.
... NewList[0] = Foo ('ccc')
>>> print NewList[0]._data
ccc
>>> print OldList[0]._data
xxx

5
这是更好、更完整的答案。 - Brian C. Lane
1
顺便说一句,我不喜欢“浅层/深层”区分。它并不能真正捕捉到问题的复杂性。深拷贝应该对其成员进行浅拷贝还是深拷贝?实际上,当您需要复制一个复杂的对象图时,通常希望克隆某些对象类型,同时仅复制其他对象的引用。 - Kos
@Kos 深拷贝始终是递归深度的 - 如果您想要一个在某个点之前是深度复制,然后变得浅的副本,那么您必须自己实现它(我不知道一个合适的术语是什么 - “沙洲”复制?!) - jonrsharpe

51

就像NXC所说的那样, Python变量名实际上指向一个对象,而不是内存中的特定位置。

newList = oldList将创建两个不同的变量,它们都指向同一个对象,因此更改oldList将同时更改newList

然而,当你执行newList = oldList[:]时,它会“切片”列表并创建一个新列表。 [:]的默认值为0和列表的结尾,因此它复制所有内容。 因此,它创建了一个包含第一个列表中所有数据的新列表,但可以更改这两个列表而不影响另一个。


7
如其他回答所述,这被称为“浅拷贝”。 - Mike Mazur
那么为什么“切片赋值也是可以的,而且这甚至可以改变列表的大小或完全清除它”?请见链接(https://docs.python.org/3.4/tutorial/introduction.html)。 - h9uest
“both can be altered without changing the other”这个说法是“仅在”涉及不可变对象的情况下才成立。如果列表包含“可变对象”,那么对新列表中的对象进行更改将反映在原始列表中,反之亦然 - 这是一个重要的区别。 - jonrsharpe
@jonrsharpe他并没有说每次更改其中一个都不会影响另一个。即使在列表中有可变对象,例如扩展一个列表也不会改变另一个列表。我认为他指的是两个列表对象本身,而不是它们的内容。 - Stefan Pochmann
@StefanPochmann 是的,但是显然它并不完全清楚,我认为这是一个值得明确的区分。 - jonrsharpe

12

正如已经回答的那样,我将简单地添加一个演示:

>>> a = [1, 2, 3, 4]
>>> b = a
>>> c = a[:]
>>> b[2] = 10
>>> c[3] = 20
>>> a
[1, 2, 10, 4]
>>> b
[1, 2, 10, 4]
>>> c
[1, 2, 3, 20]

4
永远不要认为在Python中'a = b'意味着将b复制到a。如果两边都有变量,你无法确切知道这一点。相反,应该把它看作是“给b一个额外的名字a”。
如果b是一个不可变对象(比如数字、元组或字符串),那么是的,效果就是你得到了一个副本。但这是因为当你处理不可变对象(也许应该称为只读、不可更改或WORM)时,根据定义,你总是会得到一个副本。
如果b是一个可变对象,你总是需要做一些额外的事情来确保你有一个真正的副本。总是。对于列表,最简单的方法就是使用切片:a = b[:]。
可变性也是这个原因:
def myfunction(mylist=[]): 
    pass

...并不完全按照你想象的那样运作。

如果你有C语言背景:'='左边剩下的部分始终是一个指针。所有变量始终都是指针。如果你将变量放在一个列表中:a = [b, c],那么你就把指向b和c所指向的值的指针放在了一个由a指向的列表中。如果你随后设置a[0] = d,则位于位置0的指针现在指向d所指向的任何内容。

另请参阅copy模块:http://docs.python.org/library/copy.html


2
我认为说对于不可变对象你“总是会得到一个副本”是具有误导性的,因为很少会发生任何复制。关键是让一个不可变对象拥有多个名称就像一个副本一样好,因为它不能改变其值。实际上,要制作不可变类型(例如字符串)的真正副本相当困难。 - Scott Griffiths

-2

浅拷贝:(从一个位置复制一些内存块到另一个位置)

a = ['one','two','three']

b = a[:]

b[1] = 2

print id(a), a #Output: 1077248300 ['one', 'two', 'three']
print id(b), b #Output: 1077248908 ['one', 2, 'three']

深拷贝:(复制对象引用)

a = ['one','two','three']

b = a

b[1] = 2


print id(a), a #Output: 1077248300 ['one', 2, 'three']
print id(b), b #Output: 1077248300 ['one', 2, 'three']

8
这不是深复制 - 深复制是一种递归复制 (http://docs.python.org/library/copy.html)。在列表的浅复制中,该列表的子列表不会被复制,只会重新引用。深复制会递归地进入子列表和子字典,并真正复制它们的所有条目。 - Tim Pietzcker

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