如何克隆或复制列表?

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

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

答案

使用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)
  • 您可以使用通用的copy.copy()

    import copy
    new_list = copy.copy(old_list)

    这比list()慢一点,因为它必须先找出old_list的数据类型。

  • 如果列表包含对象,并且您也想复制它们,请使用常规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 str(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\n list.copy(): %r\n slice: %r\n list(): %r\n copy: %r\n deepcopy: %r'
      % (a, b, c, d, e, f))

结果:

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

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

  1. 10.59 秒(105.9us / itn) copy.deepcopy(old_list)
  2. 10.16 秒(101.6us / itn)- 使用 Deepcopy 复制类的纯 python Copy()方法
  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[:]list(list)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+ 添加了list.copy()方法,该方法应与切片速度一样快:

newlist = old_list.copy()

在 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 中使用copy模块中的deepcopy

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

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

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)

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

使用thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>>

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

Python 3.6 计时

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

我坚持只做浅表副本,还添加了一些新的方法,这些新方法在 Python2 中是不可能的,例如list.copy()等效于 Python3 slice )和列表解 *new_list, = list两种形式( *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 赢家仍然表现良好,但并没有在很大程度上list.copy() 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))

让我们从头开始,并对其进行一些深入的探索:

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

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

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

因此,首先让我们尝试一般的复制方法:

copy=list_1

现在,如果您想将副本复制到 list_1 上,那么您可能是错的,让我们检查一下:

The id() function shows us that both variables point to the same list object, i.e. they share this object.
print(id(copy))
print(id(list_1))

输出:

4329485320
4329485320

感到惊讶吗?好的,让我们来探索一下:

因此,我们知道 python 在变量中不存储任何内容,变量只是引用对象,而对象存储值。这里的对象是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']

因此,您可以看到它并没有修改原始列表,而是仅修改了复制的列表,因此我们可以接受。

所以现在我认为我们完成了吗?等待,我们也必须复制第二个嵌套列表,所以让我们尝试 pythonic 方法:

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

现在,这有点混乱,我们使用了 pythonic 方式,但仍然面临着同样的问题。

让我们了解一下:

因此,当我们这样做时:

copy_2=list_2[:]

我们实际上仅复制外部列表,而不是嵌套列表,因此嵌套列表是两个列表的同一对象,让我们检查一下:

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

输出:

4329485832
4329485832

所以实际上,当我们执行copy_2=list_2[:] ,会发生以下情况:

在此处输入图片说明

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

那么解决方案是什么?

解决方案是deep copy

from copy import deepcopy
deep=deepcopy(list_2)

现在让我们检查一下:

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

输出:

4322146056 4322148040

两个 ID 都不相同,现在让我们检查嵌套列表 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']]

因此,您可以看到它没有修改原始的嵌套列表,而仅修改了复制的列表。

如果您喜欢我的详细答案,请通过投票让我知道,如果您对这个答案有任何疑问,请评论一下:)

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

new_list = old_list[:]copy.copy(old_list)'和 Py3k old_list.copy()用于单层列表时,它们恢复为指向嵌套在old_listnew_list中的list对象,并更改为一个list对象中的另一个被永久保留。

编辑:揭露新信息

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

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

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

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