如何深拷贝一个列表?

Posted

技术标签:

【中文标题】如何深拷贝一个列表?【英文标题】:How to deep copy a list? 【发布时间】:2013-07-26 06:04:26 【问题描述】:

E0_copy = list(E0) 之后,我猜E0_copyE0 的深层副本,因为id(E0) 不等于id(E0_copy)。然后我在循环中修改E0_copy,但是为什么后面E0不一样了?

E0 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for k in range(3):
    E0_copy = list(E0)
    E0_copy[k][k] = 0
    #print(E0_copy)
print E0  # -> [[0, 2, 3], [4, 0, 6], [7, 8, 0]]

【问题讨论】:

另外,b = a[:] 是浅拷贝。参考***.com/questions/16270374/… 【参考方案1】:

E0_copy 不是深拷贝。您不会使用 list() 进行深层复制。 (list(...)testList[:] 都是浅拷贝。)

您使用copy.deepcopy(...) 深度复制列表。

deepcopy(x, memo=None, _nil=[])
    Deep copy operation on arbitrary Python objects.

看下面的sn-p-

>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = list(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b   # b changes too -> Not a deepcopy.
[[1, 10, 3], [4, 5, 6]]

现在查看deepcopy 操作

>>> import copy
>>> b = copy.deepcopy(a)
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 10, 3], [4, 5, 6]]
>>> a[0][1] = 9
>>> a
[[1, 9, 3], [4, 5, 6]]
>>> b    # b doesn't change -> Deep Copy
[[1, 10, 3], [4, 5, 6]]

解释一下,list(...) 不会递归地复制内部对象。它只制作最外层列表的副本,同时仍然引用相同的内部列表,因此,当您改变内部列表时,更改会反映在原始列表和浅表副本中。通过检查id(a[0]) == id(b[0]) where b = list(a),您可以看到浅复制引用了内部列表。

【讨论】:

list1.append(list2) 也是list2的浅拷贝 请记住,copy.deepcopy 是 incredibly slow 相对于列表切片(大约 20 倍)。 Implementing __deepcopy__ 在课堂上可以帮助加快速度。 @Lazik 嗯?不,这不对。你从哪里得到那个的?也许你的意思是list1 = []; list1.extend(list2)【参考方案2】:

如果您的列表元素是不可变对象,那么您可以使用它,否则您必须使用来自copy 模块的deepcopy

你也可以像这样使用最短的方法来深拷贝list

a = [0,1,2,3,4,5,6,7,8,9,10]
b = a[:] #deep copying the list a and assigning it to b
print id(a)
20983280
print id(b)
12967208

a[2] = 20
print a
[0, 1, 20, 3, 4, 5, 6, 7, 8, 9,10]
print b
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10]

【讨论】:

这不是深拷贝。 那是什么。它有两个具有相同值的不同字典(您可以检查每个字典的 id)。 Read this, [:] 只是创建一个浅拷贝,它不会递归地创建其中对象的拷贝。 谢谢。你的意思是说如果我们使用这个,新列表将被创建,但新列表的所有元素将只是副本,它们将具有与前一个相同的对象(相同的 id)? 尝试使用嵌套列表。更新列表 a 的嵌套项。它也会在列表 b 中更新。这意味着 a[:] 不是深拷贝。【参考方案3】:

我相信很多程序员都遇到过面试问题,他们被要求深度复制一个链表,但是这个问题比听起来更难!

在 Python 中,有一个名为 copy 的模块具有两个有用的功能:

import copy
copy.copy()
copy.deepcopy()

copy() 是一个浅拷贝函数。如果给定的参数是一个复合数据结构,例如 list,那么 Python 将创建另一个相同类型的对象(在这种情况下,一个 new list)但是旧列表中的所有内容,仅复制它们的引用。可以这样想:

newList = [elem for elem in oldlist]

直观地说,我们可以假设deepcopy() 将遵循相同的范例,唯一的区别是对于每个 elem 我们将递归调用 deepcopy,(就像mbguy's answer)

但这是错误的!

deepcopy()实际上保留了原始复合数据的图形结构:

a = [1,2]
b = [a,a] # there's only 1 object a
c = deepcopy(b)

# check the result
c[0] is a # False, a new object a_1 is created
c[0] is c[1] # True, c is [a_1, a_1] not [a_1, a_2]

这是棘手的部分:在deepcopy() 的过程中,使用哈希表(Python 中的字典)将每个旧对象 ref 映射到每个新对象 ref,从而防止不必要的重复,从而保留复制的结构复合数据。

Official docs

【讨论】:

应更新 deepcopy 文档,以突出包含此答案末尾的警告/示例。【参考方案4】:

将列表视为一棵树,python中的deep_copy可以最简洁的写成

def deep_copy(x):
    if not isinstance(x, list):
        return x
    else:
        return [deep_copy(elem) for elem in x]

它基本上是以深度优先的方式递归遍历列表。

【讨论】:

【参考方案5】:

@Sukrit 卡拉

No.1:list()[:]copy.copy()都是浅拷贝。如果一个对象是复合的,它们都不合适。您需要使用copy.deepcopy()

No.2:直接b = aab具有相同的引用,更改a与更改b一样。

将 a 设置为 b

如果直接将a 分配给bab 共享一个引用。

>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = a
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0] = 1
>>> a
[1, [4, 5, 6]]
>>> b
[1, [4, 5, 6]]


>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = a
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 10, 3], [4, 5, 6]]

影子副本

list()

list()[:] 是一样的。除了第一层的变化,其他层的变化都会被转移。

>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = list(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0] = 1
>>> a
[1, [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]


>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = list(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 10, 3], [4, 5, 6]]

[:]

>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = a[:]
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0] = 1
>>> a
[1, [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]


>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = a[:]
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 10, 3], [4, 5, 6]]

list() 和 [:] 改变其他层,除了第一层

# =========== [:] ===========
>>> a = [[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b = a[:]
>>> a
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> a[0][2] = 4
>>> a
[[1, 2, 4], [4, 5, 6]]
>>> b
[[1, 2, 4], [4, 5, 6]]


>>> a = [[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b = a[:]
>>> a
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> a[0][2][0] = 999
>>> a
[[1, 2, [999, 6]], [4, 5, 6]]
>>> b
[[1, 2, [999, 6]], [4, 5, 6]]



# =========== list() ===========
>>> a = [[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b = list(a)
>>> a
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> a[0][2] = 4
>>> a
[[1, 2, 4], [4, 5, 6]]
>>> b
[[1, 2, 4], [4, 5, 6]]


>>> a = [[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b = list(a)
>>> a
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> b
[[1, 2, [3.5, 6]], [4, 5, 6]]
>>> a[0][2][0] = 999
>>> a
[[1, 2, [999, 6]], [4, 5, 6]]
>>> b
[[1, 2, [999, 6]], [4, 5, 6]]

copy()

你会发现copy()的功能和list()[:]是一样的。都是浅拷贝

关于浅拷贝和深拷贝的更多信息,也许你可以参考here。

>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = copy.copy(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 10, 3], [4, 5, 6]]

deepcopy()

>>> import copy
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = copy.deepcopy(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0] = 1
>>> a
[1, [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]


>>> a = [[1, 2, 3], [4, 5, 6]]
>>> b = copy.deepcopy(a)
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]
>>> a[0][1] = 10
>>> a
[[1, 10, 3], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]

【讨论】:

请edit 回答您的问题,包括解释它是如何工作的以及为什么它可以解决问题中描述的问题。见How to Answer。 Here is the much more detailed case【参考方案6】:

如果不允许直接导入模块,可以将自己的 deepcopy 函数定义为 -

def copyList(L):
if type(L[0]) != list:
    return [i for i in L]
else:
    return [copyList(L[i]) for i in range(len(L))]

它的工作很容易被视为 -

>>> x = [[1,2,3],[3,4]]
>>> z = copyList(x)
>>> x
[[1, 2, 3], [3, 4]]
>>> z
[[1, 2, 3], [3, 4]]
>>> id(x)
2095053718720
>>> id(z)
2095053718528
>>> id(x[0])
2095058990144
>>> id(z[0])
2095058992192
>>>

【讨论】:

【参考方案7】:

下面是一个如何深度复制 2D 列表的示例:

  b = [x[:] for x in a]

【讨论】:

你测试了这个例子吗? a = [3, 4, 5] b = [x[:] for x in a] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <listcomp> TypeError: 'int' object is not subscriptable 啊,我是为二维列表写的。 a = [[0,1],[2,3]]。我已经编辑了答案以突出显示这一点。【参考方案8】:

这更pythonic

my_list = [0, 1, 2, 3, 4, 5]  # some list
my_list_copy = list(my_list)  # my_list_copy and my_list does not share reference now.

注意:这对于引用对象列表是不安全的

【讨论】:

这不起作用。我认为它可能只是刚刚检查过。尝试以字典列表为例 @ShashankSingh 是的,这不适用于字典列表,因为条目是引用标签(指向内存位置)。因此,使用此方法复制字典列表将创建一个新列表,但由于条目是字典,它们仍将引用相同的内存位置。【参考方案9】:

如果列表的内容是原始数据类型,则可以使用推导式

new_list = [i for i in old_list]

您可以将它嵌套在多维列表中,例如:

new_grid = [[i for i in row] for row in grid]

【讨论】:

【参考方案10】:

只是一个递归的深拷贝函数。

def deepcopy(A):
    rt = []
    for elem in A:
        if isinstance(elem,list):
            rt.append(deepcopy(elem))
        else:
            rt.append(elem)
    return rt

编辑:正如 Cfreak 提到的,这已经在 copy 模块中实现。

【讨论】:

没有理由在copy 模块中重新实现标准的deepcopy() 函数

以上是关于如何深拷贝一个列表?的主要内容,如果未能解决你的问题,请参考以下文章

深拷贝与浅拷贝

Python 列表浅拷贝与深拷贝

深拷贝浅拷贝

python 复制 浅copy 深copy 的区别

列表操作 -深拷贝与浅拷贝

vue组件间传递对象的深拷贝