如何克隆或复制列表?


2543

在Python中克隆或复制列表有哪些选项?

在使用new_list = my_list,任何修改new_list改变my_list每次。为什么是这样?

Answers:


3321

使用new_list = my_list,您实际上没有两个列表。分配只是将引用复制到列表,而不是实际列表,因此将两者复制new_listmy_list在分配后引用同一列表。

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

  • 您可以使用内建list.copy()方法(自Python 3.3起可用):

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

    new_list = old_list[:]

    Alex Martelli对此看法(至少是在2007年)是,这是一种怪异的语法,永远不要使用它。;)(在他看来,下一个更具可读性)。

  • 您可以使用内置list()函数:

    new_list = list(old_list)
  • 您可以使用generic copy.copy()

    import copy
    new_list = copy.copy(old_list)

    这比list()因为必须找出old_listfirst 的数据类型慢一些。

  • 如果列表包含对象,并且您也想复制它们,请使用generic copy.deepcopy()

    import copy
    new_list = copy.deepcopy(old_list)

    显然,这是最慢且最需要内存的方法,但有时是不可避免的。

例:

import copy

class Foo(object):
    def __init__(self, val):
         self.val = val

    def __repr__(self):
        return 'Foo({!r})'.format(self.val)

foo = Foo(1)

a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)

# edit orignal list and instance 
a.append('baz')
foo.val = 5

print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
      % (a, b, c, d, e, f))

结果:

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)]

7
如果我没有记错的话newlist = [*mylist],Python 3也有可能newlist = list(mylist)
斯特凡

9
另一个可能性是new_list = old_list * 1
aris

4
这些方法中的哪些是浅表复制,哪些是深复制?
Eswar

4
@Eswar:除了最后一个做一个浅拷贝
费利克斯·克林

3
@Eswar这是一个浅表副本。
juanpa.arrivillaga

602

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

  1. 10.59秒(105.9us / itn)- copy.deepcopy(old_list)
  2. 10.16秒(101.6us / itn)-使用Deepcopy Copy()复制类的纯python 方法
  3. 1.488秒(14.88us / itn)-纯python Copy()方法不复制类(仅字典/列表/元组)
  4. 0.325秒(3.25us / itn)- for item in old_list: new_list.append(item)
  5. 0.217秒(2.17us / itn)- [i for i in old_list]列表理解
  6. 0.186秒(1.86us / itn)- copy.copy(old_list)
  7. 0.075秒(0.75us / itn)- list(old_list)
  8. 0.053秒(0.53us / itn)- new_list = []; new_list.extend(old_list)
  9. 0.039秒(0.39us / itn)- old_list[:]列表切片

因此最快的是列表切片。但是请注意copy.copy()list[:]和和python版本list(list)不同,和copy.deepcopy()和不会在列表中复制任何列表,字典和类实例,因此,如果原始版本更改,它们也会在复制的列表中更改,反之亦然。

(如果有人有兴趣或想提出任何问题,请使用以下脚本:)

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

9
由于您正在进行基准测试,因此包含参考点可能会有所帮助。这些数字在2017年使用Python 3.6和完全编译的代码仍然准确吗?我注意到下面的答案(stackoverflow.com/a/17810305/26219)已经对此答案提出了疑问。
马克·爱丁顿

4
使用timeit模块。同样,您不能从像这样的任意微观基准中得出很多结论。
科里·戈德堡

3
如果您想为3.5+包括一个新选项,[*old_list]应该大致等同于list(old_list),但是由于它是语法,而不是常规的函数调用路径,因此可以节省一些运行时间(与old_list[:],它不键入convert,[*old_list]对任何可迭代项起作用并产生list)。
ShadowRanger

3
@CoreyGoldberg一个稍微少任意微基准测试(用途timeit,50M的运行,而不是100K)看到stackoverflow.com/a/43220129/3745896

1
@ShadowRanger [*old_list]实际上似乎胜过几乎所有其他方法。(见我的答案在以前的评论链接)


125

在Python中克隆或复制列表有哪些选项?

在Python 3中,可以使用以下方式创建浅表副本:

a_copy = a_list.copy()

在Python 2和3中,您可以获得包含原始文档完整切片的浅表副本:

a_copy = a_list[:]

说明

复制列表有两种语义方式。浅表副本创建相同对象的新列表,深表副本创建包含新的等效对象的新列表。

浅表副本

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

在Python 2和3中有不同的方法来执行此操作。Python2的方法也将在Python 3中工作。

Python 2

在Python 2中,制作列表的浅表副本的惯用方法是使用原始列表的完整切片:

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.copy方法:

a_copy = a_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

制作另一个指针并没有进行复制

然后,每次使用my_list更改时,使用new_list = my_list都会修改new_list。为什么是这样?

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

>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]

该列表只是指向内容的指针数组,因此浅表副本仅复制指针,因此您有两个不同的列表,但是它们具有相同的内容。要复制内容,您需要一个深层副本。

深拷贝

为了使列表的深层副本,在Python 2或3时,使用deepcopy了在copy模块

import copy
a_deep_copy = copy.deepcopy(a_list)

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

>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]

因此,我们看到深度复制的列表与原始列表完全不同。您可以滚动自己的函数-但不能。通过使用标准库的Deepcopy函数,您可能会创建本来没有的bug。

不要使用 eval

您可能会将此视为深度复制的一种方法,但不要这样做:

problematic_deep_copy = eval(repr(a_list))
  1. 这很危险,特别是如果您正在评估来自不信任来源的内容时。
  2. 这是不可靠的,如果您要复制的子元素没有可以用来复制等效元素的表示形式。
  3. 它的性能也较差。

在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
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644

1
如果列表是2D,则不需要深度拷贝。如果它是列表列表,并且这些列表中没有列表,则可以使用for循环。目前,我正在使用, list_copy=[] for item in list: list_copy.append(copy(item))并且速度更快。
约翰·洛克

53

已经有很多答案可以告诉您如何制作正确的副本,但是没有一个答案说明您原来的“副本”失败的原因。

Python不会将值存储在变量中。它将名称绑定到对象。您的原始任务采用了所引用的对象并将其my_list绑定到该对象new_list。无论您使用哪个名称,都只有一个列表,因此将其引用为时所做的更改my_list将保持不变new_list。该问题的其他每个答案都为您提供了不同的方法来创建要绑定的新对象new_list

列表中的每个元素都像名称一样,因为每个元素都非排他地绑定到对象。浅表副本会创建一个新列表,其元素绑定到与以前相同的对象。

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)

有关复制中极端情况的更多信息,请参见文档



34

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

因此,假设您有两个列表:

list_1=['01','98']
list_2=[['01','98']]

我们必须复制两个列表,现在从第一个列表开始:

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

copy=list_1

现在,如果您正在考虑将副本复制到list_1,那么您错了。该id函数可以显示两个变量是否可以指向同一对象。让我们尝试一下:

print(id(copy))
print(id(list_1))

输出为:

4329485320
4329485320

这两个变量是完全相同的参数。你惊喜吗?

因此,我们知道python在变量中不存储任何内容,变量只是引用对象,而对象存储值。这里的对象是a,list但是我们通过两个不同的变量名称创建了对该对象的两个引用。这意味着两个变量都指向相同的对象,只是名称不同。

当您这样做时copy=list_1,它实际上是在做:

在此处输入图片说明

在图像list_1和副本中,这是两个变量名,但是两个变量的对象相同,即 list

因此,如果您尝试修改复制的列表,那么它也将修改原始列表,因为该列表仅存在于此列表中,无论您是从复制列表还是从原始列表进行操作,都将修改该列表:

copy[0]="modify"

print(copy)
print(list_1)

输出:

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

因此,它修改了原始列表:

现在,让我们进入用于复制列表的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)

输出为:

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

如您所见,它仅修改了复制的列表。这意味着它有效。

你认为我们完成了吗?否。让我们尝试复制嵌套列表。

copy_2=list_2[:]

list_2应该引用另一个对象,即的副本list_2。让我们检查:

print(id((list_2)),id(copy_2))

我们得到输出:

4330403592 4330403528

现在我们可以假设两个列表都指向不同的对象,所以现在让我们尝试对其进行修改,然后看看它在提供我们想要的东西:

copy_2[0][1]="modify"

print(list_2,copy_2)

这给了我们输出:

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

这似乎有点令人困惑,因为我们以前使用的相同方法有效。让我们尝试理解这一点。

当您这样做时:

copy_2=list_2[:]

您只复制外部列表,而不复制内部列表。我们可以id再次使用该功能进行检查。

print(id(copy_2[0]))
print(id(list_2[0]))

输出为:

4329485832
4329485832

当我们这样做时copy_2=list_2[:],会发生以下情况:

在此处输入图片说明

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

解决办法是什么?解决方案是deepcopy功能。

from copy import deepcopy
deep=deepcopy(list_2)

让我们检查一下:

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

4322146056 4322148040

两个外部列表都有不同的ID,让我们在内部嵌套列表上尝试一下。

print(id(deep[0]))
print(id(list_2[0]))

输出为:

4322145992
4322145800

如您所见,两个ID不同,这意味着我们可以假设两个嵌套列表现在都指向不同的对象。

这意味着当您执行deep=deepcopy(list_2)实际操作时:

在此处输入图片说明

两个嵌套列表都指向不同的对象,并且它们现在具有单独的嵌套列表副本。

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

deep[0][1]="modify"
print(list_2,deep)

它输出:

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

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



33

Python 3.6计时

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

我坚持只做浅表副本,并且还添加了一些新的方法,这些新方法在Python2中是不可能的,例如list.copy()等效于Python3 slice)和列表解包的两种形式(*new_list, = listnew_list = [*list]):

METHOD                  TIME TAKEN
b = [*a]                2.75180600000021
b = a * 1               3.50215399999990
b = a[:]                3.78278899999986  # Python2 winner (see above)
b = a.copy()            4.20556500000020  # Python3 "slice equivalent" (see above)
b = []; b.extend(a)     4.68069800000012
b = a[0:len(a)]         6.84498999999959
*b, = a                 7.54031799999984
b = list(a)             7.75815899999997
b = [i for i in a]      18.4886440000000
b = copy.copy(a)        18.8254879999999
b = []
for item in a:
  b.append(item)        35.4729199999997

我们可以看到Python2赢家仍然表现不错,但并没有在很大程度上超越Python3 list.copy(),特别是考虑到后者的优越可读性。

黑马是拆包和重新打包的方法(b = [*a]),比原始切片快25%,是其他拆包方法(*b, = a)的两倍以上。

b = a * 1 也做得很好。

请注意,这些方法对于列表以外的任何输入均不输出等效结果。它们都适用于可切片的对象,少数适用于任何可迭代的对象,但仅copy.copy()适用于更通用的Python对象。


这是有关各方的测试代码(来自此处的模板):

import timeit

COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'

print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []; for item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a:  b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))

1
可以在3.8上确认一个相似的故事b=[*a]-一种显而易见的方法;)。
SuperShoot

19

所有其他贡献者都给出了不错的答案,当您只有一个维(级别)列表时,这些方法就可以copy.deepcopy()工作,但是到目前为止,提到的方法仅适用于克隆/复制列表,而list当您处于列表中时,它不能指向嵌套对象使用多维嵌套列表(列表列表)。虽然Felix Kling在回答中提到了此问题,但问题还有很多,并且可能是使用内置方法的变通办法,它可以证明是更快的替代方法deepcopy

虽然new_list = old_list[:]copy.copy(old_list)'对于Py3k old_list.copy()适用于单层列表,它们还原为指向list嵌套在old_list和中的对象new_list,而对其中一个list对象的更改则永久存在于另一个对象中。

编辑:揭露新信息

正如Aaron HallPM 2Ring 所指出的那样,使用eval()不仅是一个坏主意,而且比慢得多copy.deepcopy()

这意味着对于多维列表,唯一的选择是copy.deepcopy()。话虽这么说,当您尝试在中等大小的多维数组上使用它时,性能确实会下降,这确实不是一个选择。我尝试timeit使用42x42的阵列,对于生物信息学应用程序,这并不是闻所未闻的,甚至还不是那么大,我放弃了等待响应,只是开始在这篇文章中输入我的编辑。

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

如其他人所述,使用模块和多维列表存在 严重的性能问题。copycopy.deepcopy


5
这并不总能奏效,因为不能保证返回的字符串repr()足以重新创建对象。另外,它eval()是不得已的手段;有关详细信息,请参见SO老手Ned Batchelder的Eval确实很危险。因此,当您提倡使用时,eval()确实应该提到它可能很危险。
下午15年

1
有道理。尽管我认为Batchelder的观点是,eval()通常在Python中具有该功能是一种风险。是否在代码中使用该函数并不重要,但这本身就是Python中的安全漏洞。我的示例未将其与从input()sys.agrv甚至文本文件接收输入的函数一起使用。它更像是一次初始化一个空白多维列表,然后只是有一种在循环中复制它的方法,而不是在循环的每次迭代中重新初始化。
AMR

1
正如@AaronHall所指出的那样,使用可能会遇到严重的性能问题new_list = eval(repr(old_list)),因此,除了这不是一个好主意之外,它的工作速度也可能太慢。
AMR

12

令我惊讶的是尚未提及,因此出于完整性考虑...

您可以使用“ 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)

1
修改副本时此方法的行为如何?
not2qubit

2
@ not2qubit是指追加或编辑新列表的元素。在示例中old_listnew_list它们是两个不同的列表,编辑一个列表不会更改另一个列表(除非您直接对元素本身进行突变(例如列表列表),这些方法都不是深层副本)。
SCB

7

已经给出的答案中缺少一种独立于python版本的非常简单的方法,您可以在大多数时间使用它(至少我可以这样做):

new_list = my_list * 1       #Solution 1 when you are not using nested lists

但是,如果my_list包含其他容器(例如,嵌套列表),则必须使用Deepcopy,如上面复制库中答案中所建议的那样。例如:

import copy
new_list = copy.deepcopy(my_list)   #Solution 2 when you are using nested lists

奖励:如果您不想复制元素,请使用(也称为浅表复制):

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

7

请注意,在某些情况下,如果您定义了自己的自定义类并且想要保留属性,则应使用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

4
new_list = my_list[:]

new_list = my_list 尝试了解这一点。假设my_list位于X位置的堆内存中,即my_list指向X。现在,通过分配new_list = my_list,让new_list指向X。这称为浅拷贝。

现在,如果您进行分配,new_list = my_list[:]您只需将my_list的每个对象复制到new_list。这称为深拷贝。

您可以执行的另一种方法是:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)

2

我想发布一些与其他答案有些不同的东西。即使这很可能不是最容易理解或最快的选择,但它提供了一些深入了解深度复制工作原理的内部视图,并且是深度复制的另一种替代选择。我的函数是否有错误并不重要,因为这样做的目的是显示一种复制对象(如问题答案)的方法,也可以以此为手段来解释深度复制在其核心中的工作方式。

深层复制功能的核心是进行浅层复制的方法。怎么样?简单。任何深层复制功能只会复制不可变对象的容器。对嵌套列表进行深度复制时,仅复制外部列表,而不复制列表内部的可变对象。您仅在复制容器。上课也一样。对类进行深度复制时,将对所有可变属性进行深度复制。又怎样?您为什么只需要复制容器,如列表,字典,元组,迭代器,类和类实例?

这很简单。可变对象实际上不能被复制。它永远不能更改,因此它只是一个值。这意味着您不必重复字符串,数字,布尔值或任何重复的字符串。但是,您将如何复制容器?简单。您只需使用所有值初始化一个新容器。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]。不可变的数字不能重复,但是另一层可以重复。您可以使用列表理解来复制它:[x表示[1、2、3]中的x

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

[x for x in _list]

它使用一个新的列表:

[deepcopy_list(x) for x in _list]

而且deepcopy_list看起来像这样:

def deepcopy_list(x):
  if isinstance(x, (str, bool, float, int)):
    return x
  else:
    return [deepcopy_list(y) for y in x]

然后,您现在有了一个函数,该函数可以使用递归str,bool,floast,int甚至列表的任何列表深复制到无限多个图层。在那里,您可以进行深度复制。

TLDR:Deepcopy使用递归来复制对象,并且仅返回与以前相同的不可变对象,因为不能复制不可变对象。但是,它将深层复制可变对象的最内层,直到到达对象的最外层可变层。


2

从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) 

2

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

    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2不是存储实际的列表,而是对list1的引用。因此,当您对list1执行任何操作时,list2也会发生变化。使用复制模块(不是默认值,可从pip下载)制作列表的原始副本(copy.copy()用于简单列表,copy.deepcopy()用于嵌套列表)。这将使副本不会随第一个列表更改。


0

deepcopy选项是唯一适用于我的方法:

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]]]
-----------------------------
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.