Python 分配后列表意外更改。这是为什么?我如何预防?

Python 分配后列表意外更改。这是为什么?我如何预防?,python,list,reference,copy,clone,Python,List,Reference,Copy,Clone,当使用new_list=my_list时,对new_list的任何修改每次都会更改my_list。这是为什么?我如何克隆或复制列表以防止它?使用thing[:] Python的习惯用法是newList=oldList[:]如果new_list=my_list,实际上没有两个列表。作业只是将引用复制到列表,而不是实际列表,因此新列表和我的列表在作业后都引用同一个列表 要实际复制列表,您有多种可能: 您可以使用Python 3.3中提供的内置方法: new_list = old_list.copy(

当使用new_list=my_list时,对new_list的任何修改每次都会更改my_list。这是为什么?我如何克隆或复制列表以防止它?

使用thing[:]


Python的习惯用法是newList=oldList[:]

如果new_list=my_list,实际上没有两个列表。作业只是将引用复制到列表,而不是实际列表,因此新列表和我的列表在作业后都引用同一个列表

要实际复制列表,您有多种可能:

您可以使用Python 3.3中提供的内置方法:

new_list = old_list.copy()
您可以将其切片:

new_list = old_list[:]
至少有人认为,这是一种奇怪的语法,永远使用它是没有意义的;在他看来,下一个更具可读性

您可以使用内置功能:

new_list = list(old_list)
您可以使用泛型:

这比list要慢一点,因为它必须首先找到旧的_list的数据类型

如果列表中包含对象,并且您也希望复制它们,请使用“通用”:

显然,这是最慢、最需要记忆的方法,但有时是不可避免的

例如:

结果:

original: ['foo', Foo(5), 'baz']
list.copy(): ['foo', Foo(5)]
slice: ['foo', Foo(5)]
list(): ['foo', Foo(5)]
copy: ['foo', Foo(5)]
deepcopy: ['foo', Foo(1)]

Felix已经提供了一个很好的答案,但我想我应该对各种方法进行速度比较:

10.59秒105.9µs/itn- 10.16第101.6µs/itn节-使用deepcopy复制类的纯Python复制方法 1.488第14.88µs/itn节-纯Python复制方法不仅复制类dicts/列表/元组 0.325秒3.25µs/itn-对于旧清单中的项目:新清单。附录项目 0.217秒2.17µs/itn-[i代表旧列表中的i]a 0.186秒1.86µs/itn- 0.075秒0.75µs/itn-列表旧列表 0.053秒0.53µs/itn-新列表=[];新列表。扩展列表 0.039秒0.39µs/itn-旧列表[:] 所以最快的是列表切片。但是请注意,copy.copy、list[:]和listlist与copy.deepcopy和python版本不同,它们不会复制列表中的任何列表、词典和类实例,因此如果原始列表发生更改,它们也会在复制的列表中发生更改,反之亦然

如果有人感兴趣或想提出任何问题,请看以下脚本:

from copy import deepcopy

class old_class:
    def __init__(self):
        self.blah = 'blah'

class new_class(object):
    def __init__(self):
        self.blah = 'blah'

dignore = {str: None, unicode: None, int: None, type(None): None}

def Copy(obj, use_deepcopy=True):
    t = type(obj)

    if t in (list, tuple):
        if t == tuple:
            # Convert to a list if a tuple to
            # allow assigning to when copying
            is_tuple = True
            obj = list(obj)
        else:
            # Otherwise just do a quick slice copy
            obj = obj[:]
            is_tuple = False

        # Copy each item recursively
        for x in xrange(len(obj)):
            if type(obj[x]) in dignore:
                continue
            obj[x] = Copy(obj[x], use_deepcopy)

        if is_tuple:
            # Convert back into a tuple again
            obj = tuple(obj)

    elif t == dict:
        # Use the fast shallow dict copy() method and copy any
        # values which aren't immutable (like lists, dicts etc)
        obj = obj.copy()
        for k in obj:
            if type(obj[k]) in dignore:
                continue
            obj[k] = Copy(obj[k], use_deepcopy)

    elif t in dignore:
        # Numeric or string/unicode?
        # It's immutable, so ignore it!
        pass

    elif use_deepcopy:
        obj = deepcopy(obj)
    return obj

if __name__ == '__main__':
    import copy
    from time import time

    num_times = 100000
    L = [None, 'blah', 1, 543.4532,
         ['foo'], ('bar',), {'blah': 'blah'},
         old_class(), new_class()]

    t = time()
    for i in xrange(num_times):
        Copy(L)
    print 'Custom Copy:', time()-t

    t = time()
    for i in xrange(num_times):
        Copy(L, use_deepcopy=False)
    print 'Custom Copy Only Copying Lists/Tuples/Dicts (no classes):', time()-t

    t = time()
    for i in xrange(num_times):
        copy.copy(L)
    print 'copy.copy:', time()-t

    t = time()
    for i in xrange(num_times):
        copy.deepcopy(L)
    print 'copy.deepcopy:', time()-t

    t = time()
    for i in xrange(num_times):
        L[:]
    print 'list slicing [:]:', time()-t

    t = time()
    for i in xrange(num_times):
        list(L)
    print 'list(L):', time()-t

    t = time()
    for i in xrange(num_times):
        [i for i in L]
    print 'list expression(L):', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(L)
    print 'list extend:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        for y in L:
            a.append(y)
    print 'list append:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(i for i in L)
    print 'generator expression extend:', time()-t
我有一个Python 3.3+方法,它应该和切片一样快:

newlist = old_list.copy()
在Python中克隆或复制列表的选项有哪些? 在Python 3中,可以通过以下方式创建浅拷贝:

a_copy = a_list.copy()
在Python2和Python3中,您可以获得一个带有原始文件完整切片的浅拷贝:

a_copy = a_list[:]
a_copy = a_list[:]
解释 复制列表有两种语义方式。浅复制创建相同对象的新列表,深复制创建包含新等效对象的新列表

浅表副本 浅表副本仅复制列表本身,列表本身是对列表中对象的引用的容器。如果包含的对象本身是可变的,并且其中一个对象发生了更改,那么更改将反映在两个列表中

在Python2和Python3中有不同的实现方法。Python2方法也可以在Python3中使用

Python 2 在Python 2中,制作列表的浅拷贝的惯用方法是使用原始列表的完整片段:

a_copy = a_list[:]
a_copy = a_list[:]
您也可以通过将列表传递给列表构造函数来完成相同的任务

a_copy = list(a_list)
但使用构造函数的效率较低:

>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844
Python 3 在Python 3中,list获取list.copy方法:

在Python 3.5中:

>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125
>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644
创建另一个指针不会创建副本 使用新列表=我的列表,然后在每次我的列表更改时修改新列表。为什么会这样

我的列表只是一个指向内存中实际列表的名称。当你说new_list=my_list时,你不是在复制,你只是在内存中添加另一个指向原始列表的名称。当我们复制列表时,可能会遇到类似的问题

>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]
列表只是指向内容的指针数组,因此浅层副本只是复制指针,因此有两个不同的列表,但它们具有相同的内容。要复制内容,您需要一份深度副本

深拷贝 作出决定:

为了演示这如何允许我们创建新的子列表:

>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]
因此,我们看到深度复制的列表与原始列表完全不同。您可以使用自己的函数,但不要这样做。使用标准库的deepcopy函数很可能会产生错误,否则就不会产生错误

from copy import deepcopy
deep = deepcopy(list_2)
不要使用eval 您可能会看到这被用作deepcopy的一种方式,但不要这样做:

problematic_deep_copy = eval(repr(a_list))
这是危险的,尤其是当你从一个你不信任的来源评估某件事情时。 如果您要复制的子元素没有可以被评估以重现等效元素的表示,那么这是不可靠的。 它的性能也比较差。 在64位Python 2.7中:

>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206
在64位Python 3.5上:

>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125
>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644

已经有很多答案告诉你如何制作一个合适的副本,但没有一个能说明你的原始“副本”失败的原因

Python不在变量中存储值;它将名称绑定到对象。您最初的任务使用了引用的对象 通过我的列表,并将其绑定到新的列表。无论您使用哪个名称,仍然只有一个列表,因此在将其称为“我的列表”时所做的更改将在将其称为“新列表”时保持不变。此问题的其他每个答案都提供了创建新对象以绑定到新列表的不同方法

列表中的每个元素都像一个名称,因为每个元素都非独占地绑定到一个对象。浅复制创建一个新列表,其元素与以前绑定到相同的对象

new_list = list(my_list)  # or my_list[:], but I prefer this syntax
# is simply a shorter way of:
new_list = [element for element in my_list]
要进一步复制列表,请复制列表引用的每个对象,并将这些元素副本绑定到新列表

import copy  
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]
这还不是深度复制,因为列表的每个元素都可能引用其他对象,就像列表绑定到其元素一样。递归复制列表中的每个元素,然后复制每个元素引用的每个其他对象,依此类推:执行深度复制

import copy
# each element must have __deepcopy__ defined for this...
new_list = copy.deepcopy(my_list)

有关复制中的角案例的更多信息,请参阅。

所有其他贡献者都给出了很好的答案,当您有一个单一维度级别的列表时,这些答案就起作用了,但是到目前为止提到的方法,当您使用多维嵌套列表时,只有copy.deepcopy可以克隆/复制列表,而不能让列表指向嵌套列表对象。虽然他在回答中提到了这一点,但问题还有一点,可能还有一个使用内置的解决方案,它可能是deepcopy更快的替代方案

当new_list=old_list[:]、copy.copyold_list'和Py3k old_list.copy用于单级列表时,它们会恢复为指向嵌套在旧_列表和新_列表中的列表对象,对其中一个列表对象的更改将永久保存在另一个列表对象中

编辑:新信息曝光 正如双方所指出的,使用eval不仅是个坏主意,而且比copy.deepcopy慢得多

这意味着对于多维列表,唯一的选项是copy.deepcopy。尽管如此,当您尝试在中等大小的多维数组上使用它时,它的性能会大大降低,因此它实际上不是一个选项。我尝试使用42x42阵列计时,这并非闻所未闻,甚至对于生物信息学应用来说也不是那么大,我放弃了等待回复,只是开始在这篇文章中键入我的编辑

看起来唯一的实际选择是初始化多个列表并独立处理它们。如果有人对如何处理多维列表复制有任何其他建议,将不胜感激

正如其他人所指出的,在多维列表中使用copy模块和copy.deepcopy存在严重的性能问题。

Python 3.6计时 下面是使用Python 3.6.8的计时结果。请记住,这些时间是相对的,而不是绝对的

我坚持只做浅层复制,还添加了一些在Python 2中不可能实现的新方法,例如list.copy Python 3和两种形式的*new_list、=list和new_list=[*list]:

我们可以看到,Python2的优胜者仍然做得很好,但并没有远远超过Python3 list.copy,特别是考虑到后者更高的可读性

黑马是拆包和重新包装方法b=[*a],它比原始切片快约25%,比另一种拆包方法*b,=a快两倍多

b=a*1的表现也出人意料地好

请注意,这些方法不会为列表以外的任何输入输出等效结果。它们都适用于可切片对象,少数适用于任何可切片对象,但只有copy.copy适用于更一般的Python对象

以下是相关方的测试代码:

新建列表=我的列表

试着去理解这一点。假设我的_列表位于堆内存中的位置X,即我的_列表指向X。现在,通过指定new_list=my_list,您可以让new_列表指向X。这称为浅拷贝

现在,如果指定new_list=my_list[:],只需将my_list中的每个对象复制到new_list。这被称为深度复制

另一种方法是:

新列表=列表旧列表 导入复制新列表=复制。深度复制旧列表
在已经给出的答案中,缺少了一种独立于python版本的非常简单的方法,您大部分时间都可以使用它,至少我是这样做的:

new_list = my_list * 1       # Solution 1 when you are not using nested lists
但是,如果my_列表包含其他容器,例如嵌套列表,则您必须使用deepcopy,正如上面答案中从复制库中建议的其他容器一样。例如:

import copy
new_list = copy.deepcopy(my_list)   # Solution 2 when you are using nested lists
。另外:如果您不想复制元素,请使用AKA shallow copy:

new_list = my_list[:]
让我们了解解决方案1和解决方案2之间的区别

>>> a = range(5)
>>> b = a*1
>>> a,b
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> a[2] = 55
>>> a,b
([0, 1, 55, 3, 4], [0, 1, 2, 3, 4])
正如您所看到的,当我们不使用嵌套列表时,解决方案1非常有效。让我们检查一下将解决方案1应用于嵌套列表时会发生什么

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   # Solution #1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       # Solution #2 - DeepCopy worked in nested list
print(id(deep[0]))
print(id(list_2[0]))

让我们从头开始,探讨这个问题

那么让我们假设您有两个列表:

list_1 = ['01', '98']
list_2 = [['01', '98']]
现在我们必须复制两份名单 从第一个列表开始:

首先,让我们尝试将变量copy设置为原始列表list_1:

现在,如果您认为复制复制了列表_1,那么您就错了。id函数可以告诉我们两个变量是否可以指向同一个对象。让我们试试这个:

print(id(copy))
print(id(list_1))
输出为:

4329485320
4329485320
['01', '98']
['modify', '98']
4329485832
4329485832
4322145992
4322145800
两个变量都是完全相同的参数。你感到惊讶吗

所以我们知道,Python不在变量中存储任何内容,变量只是引用对象,对象存储值。这里的对象是一个列表,但我们用两个不同的变量名创建了对同一对象的两个引用。这意味着两个变量都指向同一个对象,只是名称不同

当您执行copy=list_1时,它实际上正在执行以下操作:

在图像列表中,_1和copy是两个变量名,但对象对于两个变量都是相同的,即list

因此,如果您尝试修改复制的列表,那么它也会修改原始列表,因为该列表只有一个,您将修改该列表,无论您是从复制的列表还是从原始列表:

copy[0] = "modify"

print(copy)
print(list_1)
输出:

['modify', '98']
['modify', '98']
>>> [1,2,3,4,5,6]
因此,它修改了原始列表:

copy[0] = "modify"

print(copy)
print(list_1)
现在让我们转到复制列表的Pythonic方法

copy_1 = list_1[:]
此方法修复了我们遇到的第一个问题:

print(id(copy_1))
print(id(list_1))

4338792136
4338791432
所以我们可以看到我们的两个列表都有不同的id,这意味着两个变量都指向不同的对象。所以这里实际发生的是:

现在,让我们尝试修改列表,看看我们是否仍然面临前面的问题:

copy_1[0] = "modify"

print(list_1)
print(copy_1)
输出为:

4329485320
4329485320
['01', '98']
['modify', '98']
4329485832
4329485832
4322145992
4322145800
如您所见,它只修改了复制的列表。这意味着它成功了

你认为我们结束了吗?不,让我们试着复制嵌套列表

copy_2 = list_2[:]
列表_2应引用另一个对象,该对象是列表_2的副本。让我们检查一下:

print(id((list_2)), id(copy_2))
我们得到的结果是:

4330403592 4330403528
[['01', 'modify']] [['01', 'modify']]
现在我们可以假设两个列表指向不同的对象,那么现在让我们尝试修改它,看看它给出了我们想要的:

copy_2[0][1] = "modify"

print(list_2, copy_2)
这为我们提供了输出:

4330403592 4330403528
[['01', 'modify']] [['01', 'modify']]
这似乎有点让人困惑,因为我们以前使用的方法是有效的。让我们试着理解这一点

当您这样做时:

copy_2 = list_2[:]
    list1 = ['apples','bananas','pineapples']
    list2 = list1
您只是复制外部列表,而不是内部列表。我们可以再次使用id函数来检查这一点

print(id(copy_2[0]))
print(id(list_2[0]))
输出为:

4329485320
4329485320
['01', '98']
['modify', '98']
4329485832
4329485832
4322145992
4322145800
当我们复制_2=list_2[:]时,会发生以下情况:

它创建列表的副本,但仅创建外部列表副本,而不创建嵌套列表副本。这两个变量的嵌套列表是相同的,因此,如果尝试修改嵌套列表,那么它也会修改原始列表,因为两个列表的嵌套列表对象是相同的

解决办法是什么?解决方案是deepcopy函数

from copy import deepcopy
deep = deepcopy(list_2)
让我们检查一下:

print(id((list_2)), id(deep))

4322146056 4322148040
两个外部列表都有不同的ID。让我们在内部嵌套列表上试试这个

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   # Solution #1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       # Solution #2 - DeepCopy worked in nested list
print(id(deep[0]))
print(id(list_2[0]))
输出为:

4329485320
4329485320
['01', '98']
['modify', '98']
4329485832
4329485832
4322145992
4322145800
正如您所看到的,两个ID都不同,这意味着我们可以假设两个嵌套列表现在指向不同的对象

这意味着当您执行deep=deepcopylist_2时,实际会发生什么:

这两个嵌套列表指向不同的对象,它们现在有嵌套列表的单独副本

现在,让我们尝试修改嵌套列表,看看它是否解决了上一个问题:

deep[0][1] = "modify"
print(list_2, deep)
它输出:

[['01', '98']] [['01', 'modify']]

正如您所看到的,它没有修改原始嵌套列表,只是修改了复制的列表。

这让我感到惊讶,因为还没有提到这一点,所以为了完整起见

您可以使用splat操作符:*执行列表解包,它还将复制列表中的元素

old_list = [1, 2, 3]

new_list = [*old_list]

new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]
这种方法的明显缺点是,它仅在Python 3.5+中可用

但就时间而言,这似乎比其他常用方法的性能更好

x = [random.random() for _ in range(1000)]

%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]

%timeit a = [*x]

#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

请注意,在某些情况下,如果您已经定义了自己的自定义类,并且希望保留这些属性,那么您应该使用copy.copy或copy.deepcopy,而不是其他选项,例如在Python 3中:

import copy

class MyList(list):
    pass

lst = MyList([1,2,3])

lst.name = 'custom list'

d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}


for k,v in d.items():
    print('lst: {}'.format(k), end=', ')
    try:
        name = v.name
    except AttributeError:
        name = 'NA'
    print('name: {}'.format(name))
产出:

lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list

我想发布一些不同于其他答案的东西。尽管这很可能不是最容易理解或最快的选项,但它提供了一点深度复制工作原理的内部视图,并且是深度复制的另一个备选选项。我的函数是否有bug其实并不重要,因为这一点是为了展示一种复制对象(如问题答案)的方法,同时也是为了解释deepcopy的核心工作原理

任何深度复制功能的核心都是制作浅复制的方法。怎样易于理解的任何深度复制函数都只复制不可变对象的容器。深度复制嵌套列表时,只复制外部列表,而不复制列表中的可变对象。你只是在复制容器。同样的道理也适用于班级。当您深度复制一个类时,您将深度复制它的所有可变属性。那么,如何?为什么只需要复制容器,比如列表、dict、元组、iter、类和类实例

很简单。可变对象可以 真的不能复制。它永远无法更改,因此它只是一个值。这意味着您永远不必复制字符串、数字、布尔值或任何这些。但是你如何复制这些容器呢?易于理解的您只需使用所有值初始化一个新容器。Deepcopy依赖于递归。它复制所有的容器,甚至是其中包含容器的容器,直到没有留下任何容器为止。容器是一个不可变的对象

一旦你知道了这一点,完全复制一个没有任何引用的对象是非常容易的。这里有一个用于复制基本数据类型的函数,它不适用于自定义类,但您可以随时添加它

def deepcopy(x):
  immutables = (str, int, bool, float)
  mutables = (list, dict, tuple)
  if isinstance(x, immutables):
    return x
  elif isinstance(x, mutables):
    if isinstance(x, tuple):
      return tuple(deepcopy(list(x)))
    elif isinstance(x, list):
      return [deepcopy(y) for y in x]
    elif isinstance(x, dict):
      values = [deepcopy(y) for y in list(x.values())]
      keys = list(x.keys())
      return dict(zip(keys, values))
Python自己的内置deepcopy就是基于这个示例的。唯一的区别是它支持其他类型,还通过将属性复制到一个新的重复类中来支持用户类,并且还通过使用备忘录列表或字典对已经看到的对象进行引用来阻止无限递归。这就是制作深度拷贝的关键。其核心是,制作深度拷贝只是制作浅拷贝。我希望这个答案能为这个问题增添一些东西

例子

假设您有以下列表:[1,2,3]。不可变的数字不能复制,但另一层可以。您可以使用列表复制它:[1,2,3]中的x代表x]

现在,假设您有这个列表:[[1,2],[3,4],[5,6]]。这一次,您想创建一个函数,它使用递归来深度复制列表的所有层。而不是以前的列表理解:

[x for x in _list]
它使用一个新的列表:

[deepcopy_list(x) for x in _list]
deepcopy_列表如下所示:

def deepcopy_list(x):
  if isinstance(x, (str, bool, float, int)):
    return x
  else:
    return [deepcopy_list(y) for y in x]
现在有了一个函数,它可以使用递归将str、bools、floast、int甚至列表的任何列表深度复制到无限多个层


TLDR:Deepcopy使用递归复制对象,只返回与以前相同的不可变对象,因为不可变对象无法复制。但是,它深度复制可变对象的最内层,直到到达对象的最外层。

通过id和gc

>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']

>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272) 
     |           |
      -----------

>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
     |           |           |
      -----------------------

>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
     |           |           |
      -----------------------

>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word'])  # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
     |           |
      -----------

>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
     |           |           |
      -----------------------

>>> import gc
>>> gc.get_referrers(a[0]) 
[['hell', 'word'], ['hell', 'word']]  # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None) 

请记住,在Python中执行以下操作时:

copy_2 = list_2[:]
    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2不存储实际列表,而是对list1的引用。因此,当您对list1执行任何操作时,List2也会发生更改。使用复制模块not default,在pip上下载以制作listcopy.copy的原始副本,对于简单列表,复制.deepcopy用于嵌套列表。这将生成一个不会随第一个列表而更改的副本。

deepcopy选项on是唯一适合我的方法:

from copy import deepcopy

a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = deepcopy(a)
b[0][1]=[3]
print('Deep:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = a*1
b[0][1]=[3]
print('*1:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ] ]
b = a[:]
b[0][1]=[3]
print('Vector copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = list(a)
b[0][1]=[3]
print('List copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a.copy()
b[0][1]=[3]
print('.copy():')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a
b[0][1]=[3]
print('Shallow:')
print(a)
print(b)
print('-----------------------------')
导致输出:

Deep:
[[[1, 2], [1, 2], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
*1:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Vector copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
List copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
.copy():
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Shallow:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
这是因为,行new_list=my_list为变量my_list分配了一个新的引用,该变量是new_list 这类似于下面给出的C代码

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;
您应该使用复制模块创建一个新的列表

import copy
new_list = copy.deepcopy(my_list)

有一个简单的方法来处理这个问题

代码:

输出:

['modify', '98']
['modify', '98']
>>> [1,2,3,4,5,6]

我希望这对您的查询有用。

还有另一种方法可以复制到目前为止尚未列出的列表:添加空列表:l2=l+[]

我用Python 3.8测试了它:

l = [1,2,3]
l2 = l + []
print(l,l2)
l[0] = 'a'
print(l,l2)

这不是最佳答案,但有效。

使用的方法取决于被复制列表的内容。如果列表包含嵌套的dict,则deepcopy是唯一有效的方法,否则答案切片中列出的大多数方法,循环[for],复制、扩展、合并或解包将在类似的时间内工作和执行,但loop和deepcopy除外,后者执行得最差

剧本 从随机导入randint 从时间导入时间 导入副本 项目数量=100000 def副本_类型L1:列表,l2:列表: 如果l1==l2: 返回“浅” 返回“深” def运行时间开始,结束: 运行=结束-开始 返回整数*1000000 def列表_组合数据: l1=[范围项目_计数中i的数据] 开始=时间 l2=[]+l1 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'combine','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} def列表_扩展数据: l1=[范围项目_计数中i的数据] 开始=时间 l2=[] l2.extendl1 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'extend','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} def列表数据: l1=[范围项目_计数中i的数据] 开始=时间 l2=[*l1] 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'unpack','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} def列表_deepcopydata: l1=[范围项目_计数中i的数据] 开始=时间 l2=copy.deepcopyl1 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'deepcopy','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} def list_copydata: l1=[范围项目_计数中i的数据] 开始=时间 l2=list.copyl1 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'copy','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} def列表数据: l1=[范围项目_计数中i的数据] 开始=时间 l2=l1[:] 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'slice','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} def list_循环数据: l1=[范围项目_计数中i的数据] 开始=时间 l2=[] 对于rangelenl1中的i: l2.附录1[i] 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'loop','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} def list_listdata: l1=[范围项目_计数中i的数据] 开始=时间 l2=列表L1 结束=时间 如果typedata==dict: l2[0][‘测试’]。附录1 elif typedata==列表: l2.1附录 返回{'method':'list','copy_type':copy_typel1,l2, “时间”:运行时间开始,结束} 如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu': list_type=[{'list[dict]':{'test':[1,1]}, {'list[list]':[1,1]}] 存储=[] 对于列表类型中的数据: key=listdata.keys[0] store.append{key:[list\u unpackdata[key],list\u extenddata[key], 列表组合数据[键]、列表深度复制数据[键], 列表\复制数据[键]、列表\切片数据[键], 列表_loopdata[键]]} 印刷店 后果 [{list[dict]:[ {方法:解包,复制类型:浅,时间:56149}, {方法:扩展,复制_类型:浅,时间µs:52991}, {方法:合并,复制类型:浅,时间:53726}, {方法:deepcopy,复制类型:deep,时间:2702616}, {方法:复制,复制类型:浅,时间:52204}, {方法:切片,复制类型:浅层,时间:52223}, {方法:循环,复制类型:浅,时间:836928}}, {list[list]:[ {方法:解包,复制_类型:deep,时间µs:52313}, {method:extend,copy_type:deep,time_µs:52550}, {方法:合并,复制类型:深,时间:53203}, {方法:deepcopy,复制类型:deep,时间:2608560}, {方法:复制,复制类型:深,时间:53210}, {方法:切片,复制_类型:深,时间µs:52937}, {方法:循环,复制类型:深,时间:834774} ]}]
这并不总是有效的,因为无法保证repr返回的字符串足以重新创建对象。此外,评估是最后手段;有关详细信息,请参见SO资深Ned Batchelder。所以,当你提倡使用eval时,你真的应该提到它可能是危险的。尽管我认为Batchelder的观点是,在Python中使用eval函数通常是一种风险。与其说您是否在代码中使用函数,不如说它本身就是Python中的一个安全漏洞。我的示例没有将它与从input、sys.agrv甚至文本文件接收输入的函数一起使用。它更像是一次初始化一个空白多维列表,然后在循环中复制它,而不是在循环的每次迭代中重新初始化。正如@AaronHall所指出的,使用new_list=evalrepold_list可能存在一个显著的性能问题,因此除了这是一个坏主意之外,它可能也太慢了。修改副本时,此方法的行为如何?@not2qubit您的意思是添加或编辑新列表的元素。在示例中,old_list和new_list是两个不同的列表,编辑其中一个不会改变另一个,除非您直接改变元素本身,例如list of list,这些方法都不是深度副本。是的,根据文档,s.copy创建了与s[:]相同的s的浅层副本。实际上,目前,python3.8,。复制比切片稍快。请参见下面的@AaronsHall answer.@love.by.Jesus:是的,它们被扩展到了这样一个功能,即消除了每次调用方法时创建绑定方法的开销,因此调用alist.copy的成本现在是对列表类型的dict查找,然后是一个相对便宜的无参数函数调用,最终调用与切片相同的东西。切片仍然需要构建一个切片对象,然后通过类型检查和解包来做同样的事情。当然,他们正在努力,所以在3.10版本中,切片可能会再次获胜。但这一切都毫无意义;渐近性能是相同的,固定开销相对较小,因此使用哪种方法并不重要。如果列表是二维的,则不需要deepcopy。如果它是一个列表列表,而这些列表中没有列表,则可以使用for循环。目前,我正在使用list_copy=[]作为列表中的项:list_copy.appendcopyitem,而且速度更快。我仍然可以确认3.8 b=[*a]上的类似情况,这是一种显而易见的方法;。有些时间比较并不特别
当复制这样小的列表时,这是非常有意义的。使用一系列列表长度(包括一些非常大的列表长度)进行测试会提供更多信息。计时数字应该四舍五入到适当的有效位数。15位有效数字没有任何意义。我基本上只是将计时代码的原始输出粘贴在这里。似乎你的抱怨更多的是关于如何显示计时,我几乎无法控制。在处理这个话题时,我应该向下滚动到你的答案。我一直在努力弄清楚为什么我的列表副本实际上不是副本…:-谢谢这个!deepcopy必须仅在需要时使用,并且应该知道它真正的作用。正如@Georgy在下面的回答中正确指出的,对新的\u列表值的任何更改也将更改我的\u列表中的值。因此,实际上copy.deepcopy方法是唯一一个没有引用原始列表及其值的真实副本。@呃,我认为你搞错了。我没有在这里发布任何答案或评论:你是对的,它是由你编辑的,但是由@cryo发布的,很抱歉弄错了!哪一个最快?我对json列表也有同样的问题,列表中的每个元素都是json,唯一有效的元素是new_list=copy.deepcopyold_list;我写这篇文章是因为任何人都可能遇到同样的问题。谢谢这是否意味着追加和列表理解是最好的选择?我有一个包含类列表的缓存,我想获取锁,复制列表,释放锁。我希望,当缓存副本被更改时,使用内置副本来保护复制出的列表不被更改就足够了。