题 如何克隆或复制列表?


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

运用 new_list = my_list 然后修改 new_list 每次 my_list 变化。
为什么是这样?


1698
2018-04-10 08:49


起源




答案:


new_list = my_list,你实际上没有两个列表。赋值只是将引用复制到列表,而不是实际列表,所以两者都是 new_list 和 my_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]

2326
2018-04-10 08:55



@FelixKling:编辑这个答案是否有意义 list.copy 方法(从Python 3.3开始提供)?如果你不需要兼容Python 2,那么它确实是一种明显的方法。 - Mark Dickinson
@FelixKling我100%同意。对于一个重要的Python问题的答案,这个问题有点分散和过时。 - Jiminion
如果我没有误会: newlist = [*mylist] Python 3也是一种可能性。 newlist = list(mylist) 也许更清楚。 - Stéphane
另一种可能性是new_list = old_list * 1 - aris


菲利克斯已经提供了一个很好的答案,但我想我会对各种方法进行速度比较:

  1. 10.59秒(105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16秒(101.6us / itn) - 纯蟒蛇 Copy() 使用deepcopy复制类的方法
  3. 1.488秒(14.88us / itn) - 纯蟒蛇 Copy() 方法不复制类(只有dicts / lists / tuples)
  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

编辑:在基准测试中添加了新式的旧式类和dicts,并使python版本更快,并添加了更多方法,包括列表表达式和 extend()


449
2018-04-10 10:16



由于您是基准测试,因此包含参考点可能会有所帮助。使用完全编译代码的Python 3.6,这些数字在2017年是否仍然准确?我注意到下面的答案(stackoverflow.com/a/17810305/26219)已经质疑这个答案了。 - Mark Edington
使用 timeit 模块。另外,你不能从像这样的任意微基准做出很多结论。 - Corey Goldberg


我有 被告知 Python 3.3+ 增加 list.copy() 方法,应该与切片一样快:

newlist = old_list.copy()


116
2017-07-23 12:32





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

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

a_copy = a_list.copy()

在Python 2和3中,您可以获得一个带有原始片段的浅拷贝:

a_copy = a_list[:]

说明

有两种语义方法可以复制列表。浅拷贝创建相同对象的新列表,深拷贝创建包含新等效对象的新列表。

浅清单副本

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

在Python 2和3中有不同的方法可以做到这一点.Python 2方法也适用于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

做另一个指针  制作副本

使用new_list = my_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函数创建您不会遇到的错误。

不要用 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

88
2017-10-25 12:13





有很多答案已经告诉你如何制作一个正确的副本,但没有人说你为什么原来的'副本'失败了。

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)

看到 文件 有关复制中的角落案例的更多信息。


42
2017-11-23 16:45





new_list = list(old_list)


30
2018-04-10 09:03





使用 thing[:]

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

27
2018-04-10 08:53





Python这样做的习惯是 newList = oldList[:]


26
2018-04-10 08:53





所有其他贡献者都给了  答案,当你有一个单一的维度(水平)列表,但是到目前为止提到的方法时,它才有用 copy.deepcopy() 用于克隆/复制列表,而不是指向嵌套 list 处理多维嵌套列表(列表列表)时的对象。而 费利克斯克林 在他的回答中提到它,这个问题还有一些问题,可能还有一个使用内置函数的解决方法可能是一个更快的替代方法。 deepcopy

new_list = old_list[:]copy.copy(old_list)' 并为Py3k old_list.copy() 为单级列表工作,他们恢复指向 list 嵌套在。中的对象 old_list 和 new_list,并改变其中之一 list 对象在另一个中永久存在。

编辑:新信息曝光

正如两者所指出的那样 亚伦霍尔 和 PM 2Ring  运用 eval() 不仅是一个坏主意,它也比它慢得多 copy.deepcopy() 

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

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

正如其他人所说,那里   很重要 使用的性能问题 copy 模块和 copy.deepcopy  对于多维列表 尝试在不使用的情况下计算出复制多维列表的不同方法 deepcopy,(我正在研究一个课程的问题,只允许整个算法运行5秒才能获得学分),我想出了一种使用内置函数制作嵌套列表副本的方法让他们指向彼此或在 list 嵌套在它们中的对象。我用了 eval() 和 repr() 在分配中将旧列表的副本放入新列表而不创建旧列表的链接。它采取以下形式:

new_list = eval(repr(old_list))

基本上这是做什么的代表 old_list 作为字符串,然后计算字符串,就好像它是字符串表示的对象。通过这样做,没有链接到原始 list 对象是成的。一个新的 list 创建对象,每个变量指向其自己的独立对象。以下是使用二维嵌套列表的示例。

old_list = [[0 for j in range(y)] for i in range(x)] # initialize (x,y) nested list

# assign a copy of old_list to new list without them pointing to the same list object
new_list = eval(repr(old_list)) 

# make a change to new_list 
for j in range(y):
    for i in range(x):
    new_list[i][j] += 1

如果您然后检查每个列表的内容,例如4乘3列表,Python将返回

>>> new_list

[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]

>>> old_list

[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

虽然这可能不是规范或语法上正确的方法,但它似乎运作良好。我没有测试过性能,但我会猜测 eval() 和 rep() 将比运行更少的开销 deepcopy 将。


18
2017-07-10 03:51



这并不总是有效,因为不能保证返回的字符串 repr() 足以重新创建对象。也, eval() 是最后的工具;看到 Eval真的很危险 由SO老将Ned Batchelder了解详情。所以当你提倡使用时 eval() 您 真 应该提到它可能是危险的。 - PM 2Ring
有道理。虽然我认为Batchelder的观点是拥有 eval() Python中的函数通常是一种风险。您是否在代码中使用该函数并不是因为它本身就是Python中的安全漏洞。我的例子是没有使用它接收输入的函数 input(), sys.agrv,甚至是文本文件。它更像是初始化一个空白的多维列表,然后只是在循环中复制它而不是在循环的每次迭代中重新初始化。 - AMR
正如@AaronHall指出的那样,使用时可能存在严重的性能问题 new_list = eval(repr(old_list))因此,除了这是一个坏主意,它可能也太慢了。 - AMR


Python 3.6.0计时

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

我坚持只做浅拷贝,并且还添加了Python2中不可能的一些新方法,例如 list.copy() (Python3 切片等价物)和 列表拆包 (*new_list, = list):

METHOD                  TIME TAKEN
b = a[:]                6.468942025996512   #Python2 winner
b = a.copy()            6.986593422974693   #Python3 "slice equivalent"
b = []; b.extend(a)     7.309216841997113
b = a[0:len(a)]         10.916740721993847
*b, = a                 11.046738261007704
b = list(a)             11.761539687984623
b = [i for i in a]      24.66165203397395
b = copy.copy(a)        30.853400873980718
b = []
for item in a:
  b.append(item)        48.19176080400939

考虑到Python3的可读性提高,我们可以看到老赢家仍然名列前茅,但实际上并不是很大 list.copy() 做法。

请注意,这些方法可以  输出除列表以外的任何输入的等效结果。 它们都适用于可切片对象,有些可用于任何可迭代的对象,但仅适用于可切片对象 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\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", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\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 = []\nfor 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))

12
2018-04-05 01:01



他们似乎已经优化了 list Python 3.6.1中的初始化程序。我没有安装Python 3.6.0,但是 b = list(a) 得到 2.7 和 b = a[:] 得到 3.1,和 b = a.copy() 得到 3.1 (在我的Windows和Linux上使用CPython 3.6.1),所以 list() 大约快10% - Artyer
@Artyer我再次使用Python 3.6.3(WSL 16.04)测试并获得了基本相同的顺序 - River


与其他语言不同 变量和价值,Python有 名称和对象

这个说法:

a = [1,2,3]

意味着给列表(对象)一个名字 a, 和这个:

b = a

只是提供相同的对象 a 一个新名字 b,所以每当你做某事时 a,对象改变因此 b 变化。

制作一个的唯一方法  a的副本是 创建一个新对象 像其他答案已经说过的那样。

您可以看到更多相关信息 这里


11
2018-03-23 12:32