标签归档:clone

如何克隆或复制列表?

问题:如何克隆或复制列表?

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

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

What are the options to clone or copy a list in Python?

While using new_list = my_list, any modifications to new_list changes my_list everytime. Why is this?


回答 0

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

With new_list = my_list, you don’t actually have two lists. The assignment just copies the reference to the list, not the actual list, so both new_list and my_list refer to the same list after the assignment.

To actually copy the list, you have various possibilities:

  • You can use the builtin list.copy() method (available since Python 3.3):

    new_list = old_list.copy()
    
  • You can slice it:

    new_list = old_list[:]
    

    Alex Martelli’s opinion (at least back in 2007) about this is, that it is a weird syntax and it does not make sense to use it ever. ;) (In his opinion, the next one is more readable).

  • You can use the built in list() function:

    new_list = list(old_list)
    
  • You can use generic copy.copy():

    import copy
    new_list = copy.copy(old_list)
    

    This is a little slower than list() because it has to find out the datatype of old_list first.

  • If the list contains objects and you want to copy them as well, use generic copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

    Obviously the slowest and most memory-needing method, but sometimes unavoidable.

Example:

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

Result:

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

回答 1

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

Felix already provided an excellent answer, but I thought I’d do a speed comparison of the various methods:

  1. 10.59 sec (105.9us/itn) – copy.deepcopy(old_list)
  2. 10.16 sec (101.6us/itn) – pure python Copy() method copying classes with deepcopy
  3. 1.488 sec (14.88us/itn) – pure python Copy() method not copying classes (only dicts/lists/tuples)
  4. 0.325 sec (3.25us/itn) – for item in old_list: new_list.append(item)
  5. 0.217 sec (2.17us/itn) – [i for i in old_list] (a list comprehension)
  6. 0.186 sec (1.86us/itn) – copy.copy(old_list)
  7. 0.075 sec (0.75us/itn) – list(old_list)
  8. 0.053 sec (0.53us/itn) – new_list = []; new_list.extend(old_list)
  9. 0.039 sec (0.39us/itn) – old_list[:] (list slicing)

So the fastest is list slicing. But be aware that copy.copy(), list[:] and list(list), unlike copy.deepcopy() and the python version don’t copy any lists, dictionaries and class instances in the list, so if the originals change, they will change in the copied list too and vice versa.

(Here’s the script if anyone’s interested or wants to raise any issues:)

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

回答 2

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

newlist = old_list.copy()

I’ve been told that Python 3.3+ adds list.copy() method, which should be as fast as slicing:

newlist = old_list.copy()


回答 3

在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

What are the options to clone or copy a list in Python?

In Python 3, a shallow copy can be made with:

a_copy = a_list.copy()

In Python 2 and 3, you can get a shallow copy with a full slice of the original:

a_copy = a_list[:]

Explanation

There are two semantic ways to copy a list. A shallow copy creates a new list of the same objects, a deep copy creates a new list containing new equivalent objects.

Shallow list copy

A shallow copy only copies the list itself, which is a container of references to the objects in the list. If the objects contained themselves are mutable and one is changed, the change will be reflected in both lists.

There are different ways to do this in Python 2 and 3. The Python 2 ways will also work in Python 3.

Python 2

In Python 2, the idiomatic way of making a shallow copy of a list is with a complete slice of the original:

a_copy = a_list[:]

You can also accomplish the same thing by passing the list through the list constructor,

a_copy = list(a_list)

but using the constructor is less efficient:

>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844

Python 3

In Python 3, lists get the list.copy method:

a_copy = a_list.copy()

In 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

Making another pointer does not make a copy

Using new_list = my_list then modifies new_list every time my_list changes. Why is this?

my_list is just a name that points to the actual list in memory. When you say new_list = my_list you’re not making a copy, you’re just adding another name that points at that original list in memory. We can have similar issues when we make copies of lists.

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

The list is just an array of pointers to the contents, so a shallow copy just copies the pointers, and so you have two different lists, but they have the same contents. To make copies of the contents, you need a deep copy.

Deep copies

To make a deep copy of a list, in Python 2 or 3, use deepcopy in the copy module:

import copy
a_deep_copy = copy.deepcopy(a_list)

To demonstrate how this allows us to make new sub-lists:

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

And so we see that the deep copied list is an entirely different list from the original. You could roll your own function – but don’t. You’re likely to create bugs you otherwise wouldn’t have by using the standard library’s deepcopy function.

Don’t use eval

You may see this used as a way to deepcopy, but don’t do it:

problematic_deep_copy = eval(repr(a_list))
  1. It’s dangerous, particularly if you’re evaluating something from a source you don’t trust.
  2. It’s not reliable, if a subelement you’re copying doesn’t have a representation that can be eval’d to reproduce an equivalent element.
  3. It’s also less performant.

In 64 bit 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

on 64 bit 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

回答 4

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

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)

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

There are many answers already that tell you how to make a proper copy, but none of them say why your original ‘copy’ failed.

Python doesn’t store values in variables; it binds names to objects. Your original assignment took the object referred to by my_list and bound it to new_list as well. No matter which name you use there is still only one list, so changes made when referring to it as my_list will persist when referring to it as new_list. Each of the other answers to this question give you different ways of creating a new object to bind to new_list.

Each element of a list acts like a name, in that each element binds non-exclusively to an object. A shallow copy creates a new list whose elements bind to the same objects as before.

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]

To take your list copy one step further, copy each object that your list refers to, and bind those element copies to a new list.

import copy  
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]

This is not yet a deep copy, because each element of a list may refer to other objects, just like the list is bound to its elements. To recursively copy every element in the list, and then each other object referred to by each element, and so on: perform a deep copy.

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

See the documentation for more information about corner cases in copying.


回答 5

采用 thing[:]

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

Use thing[:]

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

回答 6

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

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

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

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

Let’s start from the beginning and explore this question.

So let’s suppose you have two lists:

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

And we have to copy both lists, now starting from the first list:

So first let’s try by setting the variable copy to our original list, list_1:

copy=list_1

Now if you are thinking copy copied the list_1, then you are wrong. The id function can show us if two variables can point to the same object. Let’s try this:

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

The output is:

4329485320
4329485320

Both variables are the exact same argument. Are you surprised?

So as we know python doesn’t store anything in a variable, Variables are just referencing to the object and object store the value. Here object is a list but we created two references to that same object by two different variable names. This means that both variables are pointing to the same object, just with different names.

When you do copy=list_1, it is actually doing:

Here in the image list_1 and copy are two variable names but the object is same for both variable which is list

So if you try to modify copied list then it will modify the original list too because the list is only one there, you will modify that list no matter you do from the copied list or from the original list:

copy[0]="modify"

print(copy)
print(list_1)

output:

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

So it modified the original list :

Now let’s move onto a pythonic method for copying lists.

copy_1=list_1[:]

This method fixes the first issue we had:

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

4338792136
4338791432

So as we can see our both list having different id and it means that both variables are pointing to different objects. So what actually going on here is:

Now let’s try to modify the list and let’s see if we still face the previous problem:

copy_1[0]="modify"

print(list_1)
print(copy_1)

The output is:

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

As you can see, it only modified the copied list. That means it worked.

Do you think we’re done? No. Let’s try to copy our nested list.

copy_2=list_2[:]

list_2 should reference to another object which is copy of list_2. Let’s check:

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

We get the output:

4330403592 4330403528

Now we can assume both lists are pointing different object, so now let’s try to modify it and let’s see it is giving what we want:

copy_2[0][1]="modify"

print(list_2,copy_2)

This gives us the output:

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

This may seem a little bit confusing, because the same method we previously used worked. Let’s try to understand this.

When you do:

copy_2=list_2[:]

You’re only copying the outer list, not the inside list. We can use the id function once again to check this.

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

The output is:

4329485832
4329485832

When we do copy_2=list_2[:], this happens:

It creates the copy of list but only outer list copy, not the nested list copy, nested list is same for both variable, so if you try to modify the nested list then it will modify the original list too as the nested list object is same for both lists.

What is the solution? The solution is the deepcopy function.

from copy import deepcopy
deep=deepcopy(list_2)

Let’s check this:

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

4322146056 4322148040

Both outer lists have different IDs, let’s try this on the inner nested lists.

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

The output is:

4322145992
4322145800

As you can see both IDs are different, meaning we can assume that both nested lists are pointing different object now.

This means when you do deep=deepcopy(list_2) what actually happens:

Both nested lists are pointing different object and they have separate copy of nested list now.

Now let’s try to modify the nested list and see if it solved the previous issue or not:

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

It outputs:

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

As you can see, it didn’t modify the original nested list, it only modified the copied list.


回答 7

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

Python’s idiom for doing this is newList = oldList[:]


回答 8

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

Python 3.6 Timings

Here are the timing results using Python 3.6.8. Keep in mind these times are relative to one another, not absolute.

I stuck to only doing shallow copies, and also added some new methods that weren’t possible in Python2, such as list.copy() (the Python3 slice equivalent) and two forms of list unpacking (*new_list, = list and new_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

We can see the Python2 winner still does well, but doesn’t edge out Python3 list.copy() by much, especially considering the superior readability of the latter.

The dark horse is the unpacking and repacking method (b = [*a]), which is ~25% faster than raw slicing, and more than twice as fast as the other unpacking method (*b, = a).

b = a * 1 also does surprisingly well.

Note that these methods do not output equivalent results for any input other than lists. They all work for sliceable objects, a few work for any iterable, but only copy.copy() works for more general Python objects.


Here is the testing code for interested parties (Template from here):

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

回答 9

所有其他贡献者都给出了不错的答案,当您只有一个维(级别)列表时,这些方法就可以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使用42×42的阵列,对于生物信息学应用程序,这并不是闻所未闻的,甚至还不是那么大,我放弃了等待响应,只是开始在这篇文章中输入我的编辑。

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

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

All of the other contributors gave great answers, which work when you have a single dimension (leveled) list, however of the methods mentioned so far, only copy.deepcopy() works to clone/copy a list and not have it point to the nested list objects when you are working with multidimensional, nested lists (list of lists). While Felix Kling refers to it in his answer, there is a little bit more to the issue and possibly a workaround using built-ins that might prove a faster alternative to deepcopy.

While new_list = old_list[:], copy.copy(old_list)' and for Py3k old_list.copy() work for single-leveled lists, they revert to pointing at the list objects nested within the old_list and the new_list, and changes to one of the list objects are perpetuated in the other.

Edit: New information brought to light

As was pointed out by both Aaron Hall and PM 2Ring using eval() is not only a bad idea, it is also much slower than copy.deepcopy().

This means that for multidimensional lists, the only option is copy.deepcopy(). With that being said, it really isn’t an option as the performance goes way south when you try to use it on a moderately sized multidimensional array. I tried to timeit using a 42×42 array, not unheard of or even that large for bioinformatics applications, and I gave up on waiting for a response and just started typing my edit to this post.

It would seem that the only real option then is to initialize multiple lists and work on them independently. If anyone has any other suggestions, for how to handle multidimensional list copying, it would be appreciated.

As others have stated, there are significant performance issues using the copy module and copy.deepcopy for multidimensional lists.


回答 10

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

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

It surprises me that this hasn’t been mentioned yet, so for the sake of completeness…

You can perform list unpacking with the “splat operator”: *, which will also copy elements of your list.

old_list = [1, 2, 3]

new_list = [*old_list]

new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]

The obvious downside to this method is that it is only available in Python 3.5+.

Timing wise though, this appears to perform better than other common methods.

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)

回答 11

已经给出的答案中缺少一种独立于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

A very simple approach independent of python version was missing in already given answers which you can use most of the time (at least I do):

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

However, If my_list contains other containers (for eg. nested lists) you must use deepcopy as others suggested in the answers above from the copy library. For example:

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

.Bonus: If you don’t want to copy elements use (aka shallow copy):

new_list = my_list[:]

Let’s understand difference between Solution#1 and Solution #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])

As you can see Solution #1 worked perfectly when we were not using the nested lists. Let’s check what will happen when we apply solution #1 to nested lists.

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

回答 12

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

Note that there are some cases where if you have defined your own custom class and you want to keep the attributes then you should use copy.copy() or copy.deepcopy() rather than the alternatives, for example in 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))

Outputs:

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

回答 13

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)
new_list = my_list[:]

new_list = my_list Try to understand this. Let’s say that my_list is in the heap memory at location X i.e. my_list is pointing to the X. Now by assigning new_list = my_list you’re Letting new_list pointing to the X. This is known as shallow Copy.

Now if you assign new_list = my_list[:] You’re simply copying each object of my_list to new_list. This is known as Deep copy.

The Other way you can do this are :

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

回答 14

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

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

这很简单。可变对象实际上不能被复制。它永远不能更改,因此它只是一个值。这意味着您不必重复字符串,数字,布尔值或任何重复的字符串。但是,您将如何复制容器?简单。您只需使用所有值初始化一个新容器。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使用递归来复制对象,并且仅返回与以前相同的不可变对象,因为不能复制不可变对象。但是,它将深层复制可变对象的最内层,直到到达对象的最外层可变层。

I wanted to post something a bit different then some of the other answers. Even though this is most likely not the most understandable, or fastest option, it provides a bit of an inside view of how deep copy works, as well as being another alternative option for deep copying. It doesn’t really matter if my function has bugs, since the point of this is to show a way to copy objects like the question answers, but also to use this as a point to explain how deepcopy works at its core.

At the core of any deep copy function is way to make a shallow copy. How? Simple. Any deep copy function only duplicates the containers of immutable objects. When you deepcopy a nested list, you are only duplicating the outer lists, not the mutable objects inside of the lists. You are only duplicating the containers. The same works for classes, too. When you deepcopy a class, you deepcopy all of its mutable attributes. So, how? How come you only have to copy the containers, like lists, dicts, tuples, iters, classes, and class instances?

It’s simple. A mutable object can’t really be duplicated. It can never be changed, so it is only a single value. That means you never have to duplicate strings, numbers, bools, or any of those. But how would you duplicate the containers? Simple. You make just initialize a new container with all of the values. Deepcopy relies on recursion. It duplicates all the containers, even ones with containers inside of them, until no containers are left. A container is an immutable object.

Once you know that, completely duplicating an object without any references is pretty easy. Here’s a function for deepcopying basic data-types (wouldn’t work for custom classes but you could always add that)

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’s own built-in deepcopy is based around that example. The only difference is it supports other types, and also supports user-classes by duplicating the attributes into a new duplicate class, and also blocks infinite-recursion with a reference to an object it’s already seen using a memo list or dictionary. And that’s really it for making deep copies. At its core, making a deep copy is just making shallow copies. I hope this answer adds something to the question.

EXAMPLES

Say you have this list: [1, 2, 3]. The immutable numbers cannot be duplicated, but the other layer can. You can duplicate it using a list comprehension: [x for x in [1, 2, 3]

Now, imagine you have this list: [[1, 2], [3, 4], [5, 6]]. This time, you want to make a function, which uses recursion to deep copy all layers of the list. Instead of the previous list comprehension:

[x for x in _list]

It uses a new one for lists:

[deepcopy_list(x) for x in _list]

And deepcopy_list looks like this:

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

Then now you have a function which can deepcopy any list of strs, bools, floast, ints and even lists to infinitely many layers using recursion. And there you have it, deepcopying.

TLDR: Deepcopy uses recursion to duplicate objects, and merely returns the same immutable objects as before, as immutable objects cannot be duplicated. However, it deepcopies the most inner layers of mutable objects until it reaches the outermost mutable layer of an object.


回答 15

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

A slight practical perspective to look into memory through id and 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) 

回答 16

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

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

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

Remember that in Python when you do:

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

List2 isn’t storing the actual list, but a reference to list1. So when you do anything to list1, list2 changes as well. use the copy module (not default, download on pip) to make an original copy of the list(copy.copy() for simple lists, copy.deepcopy() for nested ones). This makes a copy that doesn’t change with the first list.


回答 17

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

The deepcopy option is the only method that works for me:

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('-----------------------------')

leads to output of:

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