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

Python list slice syntax used for no obvious reason

提问人:Charles Anderson 提问时间:11/27/2008 最后编辑:Joachim SauerCharles Anderson 更新时间:5/20/2010 访问量:12330

问:

我偶尔会看到 Python 代码中使用的列表切片语法,如下所示:

newList = oldList[:]

当然,这与以下情况相同:

newList = oldList

还是我错过了什么?

python 列表 浅拷贝

评论

1赞 Martin Thoma 3/15/2011
我在我的博客中举了一个小例子:martin-thoma.blogspot.com/2011/03/......
2赞 jonrsharpe 5/14/2015
@Abgan怎么不健全?aList[:]

答:

53赞 ConcernedOfTunbridgeWells 11/27/2008 #1

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

深层复制也会复制所有列表成员。

下面的代码片段显示了一个运行中的浅拷贝。

# ================================================================
# === 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 11/28/2008
这是更好、更完整的答案。
1赞 Kos 1/6/2013
顺便说一句,我不喜欢“浅/深”的区别。它并没有真正捕捉到问题的复杂性。深层副本应该做其成员的浅层副本还是深层副本?在实践中,当您需要复制复杂的对象图时,您通常希望克隆某些对象类型,而只复制对其他对象类型的引用。
0赞 jonrsharpe 5/14/2015
@Kos,一个深拷贝总是递归深的——如果你想要一个深到某个点然后变得浅的拷贝,你必须自己实现它(我不知道一个合适的术语是什么——“沙洲”拷贝?!
51赞 Deinumite 11/27/2008 #2

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

newList = oldList将创建指向同一对象的两个不同变量,因此,更改也会更改。oldListnewList

但是,当您这样做时,它会“切片”列表,并创建一个新列表。默认值为 0 和列表的末尾,因此它会复制所有内容。因此,它会创建一个新列表,其中包含第一个列表中包含的所有数据,但可以在不更改另一个的情况下更改两个列表。newList = oldList[:][:]

评论

7赞 Mike Mazur 7/23/2009
正如其他答案中提到的,这被称为“浅拷贝”。
0赞 h9uest 5/13/2015
那么为什么“也可以分配给切片,这甚至可以更改列表的大小或完全清除它”?参见链接(docs.python.org/3.4/tutorial/introduction.html)
0赞 jonrsharpe 5/14/2015
“两者都可以在不改变对方的情况下进行更改”——才是正确的,因为所讨论的列表包含不可变的对象。如果它包含可变对象,那么对对象的更改将反映在 中,反之亦然 - 这是一个重要的区别。newListoldList
0赞 Stefan Pochmann 5/14/2015
@jonrsharpe 他没有说一个人的每一次改变都不会改变另一个人。例如,即使列表中有可变对象,您也可以扩展一个列表,但这不会更改另一个列表。我认为他指的是这两个列表对象本身,而不是它们的内容。
0赞 jonrsharpe 5/14/2015
@StefanPochmann是的,但显然这并不完全清楚,我认为这是一个值得明确的区别。
12赞 user21037 11/27/2008 #3

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

>>> 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赞 kaleissin 11/27/2008 #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-module: http://docs.python.org/library/copy.html

评论

2赞 Scott Griffiths 3/5/2010
我认为对于不可变对象,你“总是得到一个副本”的说法是误导性的,因为任何复制都不太可能发生。关键是,让一个不可变的对象有多个名称与复制一样好,因为它不能改变它的值。实际上,制作不可变类型(例如字符串)的真正副本是相当困难的。
-2赞 abhiomkar #5

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

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赞 Tim Pietzcker 5/20/2010
这不是深层拷贝 - 深层拷贝是递归拷贝 (docs.python.org/library/copy.html)。在列表的浅拷贝中,不会复制该列表的子列表,只会重新引用。深层拷贝递归地下降到子列表和子指令中,并真正复制它们的所有条目。