如何克隆列表,使其在分配后不会意外更改?

发布于 2024-08-28 17:40:00 字数 285 浏览 14 评论 0 原文

使用 new_list = my_list 时,对 new_list 的任何修改每次都会更改 my_list。这是为什么?如何克隆或复制列表来防止这种情况发生?例如:

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

While using new_list = my_list, any modifications to new_list changes my_list every time. Why is this, and how can I clone or copy the list to prevent it? For example:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(24

祁梦 2024-09-04 17:40:00

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()< /a> 构造函数:

    new_list = 列表(old_list)
    

  • 您可以使用通用copy.copy()

    导入副本
    新列表 = copy.copy(旧列表)
    

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

  • 如果您还需要复制列表的元素,请使用通用 <代码>copy.deepcopy()

    导入副本
    new_list = copy.deepcopy(old_list)
    

    显然这是最慢且最需要内存的方法,但有时是不可避免的。这是递归操作的;它将处理任意数量的嵌套列表(或其他容器)的级别。

示例:

import copy

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

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

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(f'original: {a}\nlist.copy(): {b}\nslice: {c}\nlist(): {d}\ncopy: {e}\ndeepcopy: {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)]

new_list = my_list doesn't actually create a second list. 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 several options:

  • You can use the built-in 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() constructor:

    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 you need to copy the elements of the list 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. This operates recursively; it will handle any number of levels of nested lists (or other containers).

Example:

import copy

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

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

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(f'original: {a}\nlist.copy(): {b}\nslice: {c}\nlist(): {d}\ncopy: {e}\ndeepcopy: {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)]
哥,最终变帅啦 2024-09-04 17:40:00

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

  1. 10.59 秒(105.9 µs/itn) - copy.deepcopy(old_list)
  2. 10.16 秒 (101.6 µs/itn) - 纯 Python Copy()使用 deepcopy 复制类的方法
  3. 1.488 秒 (14.88 µs/itn) - 纯 Python Copy() 方法不复制类(仅字典/列表/元组)
  4. 0.325 秒 (3.25 µs/itn) - 对于 old_list 中的项目:new_list.append(item)
  5. 0.217 秒 (2.17 µs/itn) - [i for i in old_list] (a href="http://docs .python.org/tutorial/datastructs.html#list-compressiveons" rel="noreferrer">列表理解)
  6. 0.186 秒 (1.86 µs/itn) - copy.copy(old_list)
  7. 0.075 秒 (0.75 µs/itn) - list(old_list) )
  8. 0.053 秒 (0.53 µs/itn) - new_list = []; new_list.extend(old_list)
  9. 0.039 秒 (0.39 µs/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

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

  1. 10.59 sec (105.9 µs/itn) - copy.deepcopy(old_list)
  2. 10.16 sec (101.6 µs/itn) - pure Python Copy() method copying classes with deepcopy
  3. 1.488 sec (14.88 µs/itn) - pure Python Copy() method not copying classes (only dicts/lists/tuples)
  4. 0.325 sec (3.25 µs/itn) - for item in old_list: new_list.append(item)
  5. 0.217 sec (2.17 µs/itn) - [i for i in old_list] (a list comprehension)
  6. 0.186 sec (1.86 µs/itn) - copy.copy(old_list)
  7. 0.075 sec (0.75 µs/itn) - list(old_list)
  8. 0.053 sec (0.53 µs/itn) - new_list = []; new_list.extend(old_list)
  9. 0.039 sec (0.39 µs/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
负佳期 2024-09-04 17:40:00

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

newlist = old_list.copy()

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

newlist = old_list.copy()
清风不识月 2024-09-04 17:40:00

在 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

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

Python 另一个指针进行复制

使用 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 模块中的 /code>:

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

因此我们看到深度复制的列表是与原始列表完全不同的列表。您可以推出自己的函数 - 但不要这样做。您可能会产生使用标准库的深度复制函数不会出现的错误。

不要使用 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
莫相离 2024-09-04 17:40:00

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

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

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 不会在变量中存储任何内容,变量只是引用对象,而对象存储值。这里的对象是一个列表,但我们通过两个不同的变量名创建了对同一对象的两个引用。这意味着两个变量都指向同一个对象,只是名称不同。

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

在此处输入图像描述

图像中的 list_1copy 是两个变量名称,但两个变量的对象是相同的,即 list

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

copy[0] = "modify"

print(copy)
print(list_1)

输出:

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

所以它修改了原始列表list:

现在让我们转向复制列表的 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[:] 时,会发生这种情况:

< img src="https://i.sstatic.net/3hPti.jpg" alt="在此处输入图像描述">

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

解决办法是什么?解决方案是“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:

Enter image description here

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:

Enter image description here

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:

Enter image description here

It creates the copy of list, but only outer list copy, not the nested list copy. The 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:

Enter image description here

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.

那支青花 2024-09-04 17:40:00

已经有很多答案告诉您如何制作正确的副本,但没有一个答案说明为什么您的原始“副本”失败。

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.

温馨耳语 2024-09-04 17:40:00

使用事物[:]

>>> 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]
>>> 
探春 2024-09-04 17:40:00

Python 3.6 计时

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

我坚持只做浅拷贝,并且还添加了一些 Python 2 中不可能的新方法,例如 list.copy() (Python 3 切片等效)和两种形式的 列表解包*new_list, = listnew_list = [*list]):

METHOD                TIME TAKEN
b = [*a]               2.75180600000021
b = a * 1              3.50215399999990
b = a[:]               3.78278899999986  # Python 2 winner (see above)
b = a.copy()           4.20556500000020  # Python 3 "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

我们可以可以看出,Python 2 的获胜者仍然表现出色,但并没有比 Python 3 list.copy() 优势太多,特别是考虑到后者具有出色的可读性。

黑马是解包和重新打包方法 (b = [*a]),它比原始切片快约 25%,是其他解包方法 ( *b, = a)。

b = a * 1 的表现也出奇的好。

请注意,这些方法不会为列表以外的任何输入输出等效结果。它们都适用于可切片对象,少数适用于任何可迭代对象,但仅限于复制.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 Python 2, such as list.copy() (the Python 3 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  # Python 2 winner (see above)
b = a.copy()           4.20556500000020  # Python 3 "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 Python 2 winner still does well, but doesn't edge out Python 3 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))
写给空气的情书 2024-09-04 17:40:00

Python 执行此操作的习惯用法是 newList = oldList[:]

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

心安伴我暖 2024-09-04 17:40:00

所有其他贡献者都给出了很棒答案,当您有一个单一维度(分级)列表时,这些答案有效,但是到目前为止提到的方法中,只有copy.deepcopy()当您使用多维嵌套列表(列表的列表)时,可以克隆/复制列表,而不是让它指向嵌套的 list 对象。虽然 Felix Kling 在他的回答中提到了这一点,但这个问题还有更多内容,并且可能有一个解决方法内置函数可能被证明是deepcopy的更快替代方案。

虽然 new_list = old_list[:]copy.copy(old_list)' 和 Py3k old_list.copy() 适用于单级列表,它们恢复为指向嵌套在 old_listnew_list 中的 list 对象,并更改为 list 之一code> 对象在另一个中永久存在。

编辑:新信息曝光

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

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

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

正如其他人所说,使用 copy 模块和 copy.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 42x42 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.

愁以何悠 2024-09-04 17:40:00

令我惊讶的是,这一点还没有被提及,所以为了完整起见......

您可以使用“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)
眼眸印温柔 2024-09-04 17:40:00
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 = 列表(old_list)
    
  • 导入副本
    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 point to the X. This is known as a 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 a deep copy.

The other ways you can do this are:

  • new_list = list(old_list)
    
  • import copy
    new_list = copy.deepcopy(old_list)
    
无所谓啦 2024-09-04 17:40:00

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

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

但是,if 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 example, 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
往事随风而去 2024-09-04 17:40:00

我想发布一些与其他答案略有不同的内容。尽管这很可能不是最容易理解或最快的选项,但它提供了深度复制如何工作的一些内部视图,并且是深度复制的另一种替代选项。我的函数是否有错误并不重要,因为这样做的目的是展示一种复制问题答案等对象的方法,同时也用它来解释深度复制的核心工作原理。

任何深复制功能的核心都是进行浅复制的方法。如何?简单的。任何深度复制函数仅复制不可变对象的容器。当您深度复制嵌套列表时,您只是复制外部列表,而不是列表内部的可变对象。您只是复制容器。对于班级来说也是如此。当您深度复制一个类时,您会深度复制它的所有可变属性。那么,怎么样?为什么你只需要复制容器,比如列表、字典、元组、迭代器、类和类实例?

这很简单。可变对象实际上无法被复制。它永远无法更改,因此它只是一个值。这意味着您永远不必重复字符串、数字、布尔值或其中任何一个。但是如何复制容器呢?简单的。您只需使用所有值初始化一个新容器。 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 自己的内置深度复制基于该示例。唯一的区别是它支持其他类型,并且还通过将属性复制到新的重复类中来支持用户类,并且还通过使用备忘录列表或字典引用已经看到的对象来阻止无限递归。这就是制作深拷贝的真正目的。从本质上讲,制作深复制只是制作浅复制。我希望这个答案能为问题增添一些内容。

示例

假设您有以下列表:[1, 2, 3]。不可变数字不能重复,但另一层可以。您可以使用列表理解来复制它: [x for x in [1, 2, 3]]

现在,假设您有这个列表: [[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]

那么现在你有一个可以深度复制 strs、bools、floast、ints 的任何列表的函数甚至使用递归列出无限多个层。这就是深度复制。

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

I wanted to post something a bit different than 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.

烟若柳尘 2024-09-04 17:40:00

请注意,在某些情况下,如果您定义了自己的自定义类并且想要保留属性,那么您应该使用 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
向地狱狂奔 2024-09-04 17:40:00

请记住,在 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.

瀟灑尐姊 2024-09-04 17:40:00

通过 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) 
梦冥 2024-09-04 17:40:00

这是因为,行 new_list = my_list 为变量 my_list 分配了一个新引用,即 new_list
这类似于下面给出的 C 代码,

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

您应该使用复制模块来创建一个新列表

import copy
new_list = copy.deepcopy(my_list)

This is because, the line new_list = my_list assigns a new reference to the variable my_list which is new_list
This is similar to the C code given below,

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

You should use the copy module to create a new list by

import copy
new_list = copy.deepcopy(my_list)
你爱我像她 2024-09-04 17:40:00

还有另一种方法可以复制之前未列出的列表:添加一个空列表:l2 = l + []

我用Python 3.8测试了它:

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

这不是最好的答案,但它有效。

There is another way of copying a list that was not listed until now: adding an empty list: l2 = l + [].

I tested it with Python 3.8:

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

It is not the best answer, but it works.

自我难过 2024-09-04 17:40:00

深度复制选项是唯一适合我的方法:

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]]]
-----------------------------
饭团 2024-09-04 17:40:00

使用的方法取决于要复制的列表的内容。如果列表包含嵌套的dicts,则深度复制是唯一有效的方法,否则答案中列出的大多数方法(切片、循环[for]、复制、扩展、组合或解包)都将有效并以相似的时间执行(循环和深度复制除外,它们的执行情况最差)。

脚本

from random import randint
from time import time
import copy

item_count = 100000

def copy_type(l1: list, l2: list):
  if l1 == l2:
    return 'shallow'
  return 'deep'

def run_time(start, end):
  run = end - start
  return int(run * 1000000)

def list_combine(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [] + l1
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'combine', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_extend(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  l2.extend(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'extend', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_unpack(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [*l1]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'unpack', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_deepcopy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = copy.deepcopy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'deepcopy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_copy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list.copy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'copy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_slice(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = l1[:]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'slice', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_loop(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  for i in range(len(l1)):
    l2.append(l1[i])
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'loop', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_list(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'list()', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

if __name__ == '__main__':
  list_type = [{'list[dict]': {'test': [1, 1]}}, 
          {'list[list]': [1, 1]}]
  store = []
  for data in list_type:
    key = list(data.keys())[0]
    store.append({key: [list_unpack(data[key]), list_extend(data[key]), 
                list_combine(data[key]), list_deepcopy(data[key]), 
                list_copy(data[key]), list_slice(data[key]),           
                list_loop(data[key])]})
  print(store)

结果

[{"list[dict]": [
  {"method": "unpack", "copy_type": "shallow", "time_µs": 56149},
  {"method": "extend", "copy_type": "shallow", "time_µs": 52991},
  {"method": "combine", "copy_type": "shallow", "time_µs": 53726},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2702616},
  {"method": "copy", "copy_type": "shallow", "time_µs": 52204},
  {"method": "slice", "copy_type": "shallow", "time_µs": 52223},
  {"method": "loop", "copy_type": "shallow", "time_µs": 836928}]},
{"list[list]": [
  {"method": "unpack", "copy_type": "deep", "time_µs": 52313},
  {"method": "extend", "copy_type": "deep", "time_µs": 52550},
  {"method": "combine", "copy_type": "deep", "time_µs": 53203},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2608560},
  {"method": "copy", "copy_type": "deep", "time_µs": 53210},
  {"method": "slice", "copy_type": "deep", "time_µs": 52937},
  {"method": "loop", "copy_type": "deep", "time_µs": 834774}
]}]

The method to use depends on the contents of the list being copied. If the list contains nested dicts than deepcopy is the only method that works, otherwise most of the methods listed in the answers (slice, loop [for], copy, extend, combine, or unpack) will work and execute in similar time (except for loop and deepcopy, which preformed the worst).

Script

from random import randint
from time import time
import copy

item_count = 100000

def copy_type(l1: list, l2: list):
  if l1 == l2:
    return 'shallow'
  return 'deep'

def run_time(start, end):
  run = end - start
  return int(run * 1000000)

def list_combine(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [] + l1
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'combine', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_extend(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  l2.extend(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'extend', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_unpack(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [*l1]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'unpack', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_deepcopy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = copy.deepcopy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'deepcopy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_copy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list.copy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'copy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_slice(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = l1[:]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'slice', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_loop(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  for i in range(len(l1)):
    l2.append(l1[i])
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'loop', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_list(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'list()', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

if __name__ == '__main__':
  list_type = [{'list[dict]': {'test': [1, 1]}}, 
          {'list[list]': [1, 1]}]
  store = []
  for data in list_type:
    key = list(data.keys())[0]
    store.append({key: [list_unpack(data[key]), list_extend(data[key]), 
                list_combine(data[key]), list_deepcopy(data[key]), 
                list_copy(data[key]), list_slice(data[key]),           
                list_loop(data[key])]})
  print(store)

Results

[{"list[dict]": [
  {"method": "unpack", "copy_type": "shallow", "time_µs": 56149},
  {"method": "extend", "copy_type": "shallow", "time_µs": 52991},
  {"method": "combine", "copy_type": "shallow", "time_µs": 53726},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2702616},
  {"method": "copy", "copy_type": "shallow", "time_µs": 52204},
  {"method": "slice", "copy_type": "shallow", "time_µs": 52223},
  {"method": "loop", "copy_type": "shallow", "time_µs": 836928}]},
{"list[list]": [
  {"method": "unpack", "copy_type": "deep", "time_µs": 52313},
  {"method": "extend", "copy_type": "deep", "time_µs": 52550},
  {"method": "combine", "copy_type": "deep", "time_µs": 53203},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2608560},
  {"method": "copy", "copy_type": "deep", "time_µs": 53210},
  {"method": "slice", "copy_type": "deep", "time_µs": 52937},
  {"method": "loop", "copy_type": "deep", "time_µs": 834774}
]}]
以往的大感动 2024-09-04 17:40:00

框架挑战:您的应用程序真的需要复制吗?

我经常看到尝试以某种迭代方式修改列表副本的代码。为了构造一个简单的例子,假设我们有一个不起作用的代码(因为 x 不应该被修改),例如:

x = [8, 6, 7, 5, 3, 0, 9]
y = x
for index, element in enumerate(y):
    y[index] = element * 2
# Expected result:
# x = [8, 6, 7, 5, 3, 0, 9] <-- this is where the code is wrong.
# y = [16, 12, 14, 10, 6, 0, 18]

人们自然会问如何使 y 成为 y 的副本code>x,而不是同一列表的名称,以便 for 循环执行正确的操作。

但这是错误的做法。从功能上讲,我们真正想要做的是创建一个基于原始列表的新列表

我们不需要先制作副本来做到这一点,而且通常也不应该这样做。

当我们需要将逻辑应用于每个元素时,

最自然的工具就是列表理解。通过这种方式,我们编写了逻辑来告诉我们所需结果中的元素如何与原始元素相关。简洁、优雅、富有表现力;我们避免了在 for 循环中修改 y 副本的需要(因为 分配给迭代变量不会影响列表 - 与我们首先想要副本的原因相同!)。

对于上面的例子,它看起来像:

x = [8, 6, 7, 5, 3, 0, 9]
y = [element * 2 for element in x]

列表推导式非常强大;我们还可以使用它们通过带有 if 子句的规则过滤掉元素,并且我们可以链接 forif 子句(它的工作方式类似于相应的命令式代码,具有相同的子句以相同的顺序;只有最终出现在结果列表中的值被移动到前面,而不是位于“最里面”的部分)。如果计划是在修改副本的同时迭代原始版本以避免出现问题,通常有一种更令人愉快的方法具有过滤列表理解。

当我们需要按位置拒绝或插入特定元素时

假设我们有类似的东西

x = [8, 6, 7, 5, 3, 0, 9]
y = x
del y[2:-2] # oops, x was changed inappropriately

而不是首先将 y 制作为单独的副本以删除我们不想要的部分,我们可以构建一个列表将我们想要的部分放在一起。因此:

x = [8, 6, 7, 5, 3, 0, 9]
y = x[:2] + x[-2:]

通过切片处理插入、替换等就留作练习了。只需推理出您希望结果包含哪些子序列即可。一个特殊情况是制作反向副本 - 假设我们根本需要一个新列表(而不仅仅是反向迭代),我们可以直接通过切片来创建它,而不是克隆然后使用.reverse


这些方法 - 如列表理解 - 还具有以下优点:它们将所需结果创建为表达式,而不是通过程序就地修改现有对象(以及 返回)。这对于以“流畅”的风格编写代码来说更加方便。

Frame challenge: do you actually need to copy, for your application?

I often see code that tries to modify a copy of the list in some iterative fashion. To construct a trivial example, suppose we had non-working (because x should not be modified) code like:

x = [8, 6, 7, 5, 3, 0, 9]
y = x
for index, element in enumerate(y):
    y[index] = element * 2
# Expected result:
# x = [8, 6, 7, 5, 3, 0, 9] <-- this is where the code is wrong.
# y = [16, 12, 14, 10, 6, 0, 18]

Naturally people will ask how to make y be a copy of x, rather than a name for the same list, so that the for loop will do the right thing.

But this is the wrong approach. Functionally, what we really want to do is make a new list that is based on the original.

We don't need to make a copy first to do that, and we typically shouldn't.

When we need to apply logic to each element

The natural tool for this is a list comprehension. This way, we write the logic that tells us how the elements in the desired result, relate to the original elements. It's simple, elegant and expressive; and we avoid the need for workarounds to modify the y copy in a for loop (since assigning to the iteration variable doesn't affect the list - for the same reason that we wanted the copy in the first place!).

For the above example, it looks like:

x = [8, 6, 7, 5, 3, 0, 9]
y = [element * 2 for element in x]

List comprehensions are quite powerful; we can also use them to filter out elements by a rule with an if clause, and we can chain for and if clauses (it works like the corresponding imperative code, with the same clauses in the same order; only the value that will ultimately end up in the result list, is moved to the front instead of being in the "innermost" part). If the plan was to iterate over the original while modifying the copy to avoid problems, there is generally a much more pleasant way to do that with a filtering list comprehension.

When we need to reject or insert specific elements by position

Suppose instead that we had something like

x = [8, 6, 7, 5, 3, 0, 9]
y = x
del y[2:-2] # oops, x was changed inappropriately

Rather than making y a separate copy first in order to delete the part we don't want, we can build a list by putting together the parts that we do want. Thus:

x = [8, 6, 7, 5, 3, 0, 9]
y = x[:2] + x[-2:]

Handling insertion, replacement etc. by slicing is left as an exercise. Just reason out which subsequences you want the result to contain. A special case of this is making a reversed copy - assuming we need a new list at all (rather than just to iterate in reverse), we can directly create it by slicing, rather than cloning and then using .reverse.


These approaches - like the list comprehension - also have the advantage that they create the desired result as an expression, rather than by procedurally modifying an existing object in-place (and returning None). This is more convenient for writing code in a "fluent" style.

ˉ厌 2024-09-04 17:40:00

对每种复制模式的简短说明:

浅复制构造一个新的复合对象,然后(尽可能)将引用插入到原始对象中 - 创建浅复制:

new_list = my_list

A 深层复制构造一个新的复合对象,然后递归地将原始对象的副本插入其中 - 创建深层复制:

new_list = list(my_list)

list()适用于深层简单列表的副本,例如:

my_list = ["A","B","C"]

但是,对于复杂列表,例如...

my_complex_list = [{'A' : 500, 'B' : 501},{'C' : 502}]

...使用 deepcopy()

import copy
new_complex_list = copy.deepcopy(my_complex_list)

Short and simple explanations of each copy mode:

A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original - creating a shallow copy:

new_list = my_list

A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original - creating a deep copy:

new_list = list(my_list)

list() works fine for deep copy of simple lists, like:

my_list = ["A","B","C"]

But, for complex lists like...

my_complex_list = [{'A' : 500, 'B' : 501},{'C' : 502}]

...use deepcopy():

import copy
new_complex_list = copy.deepcopy(my_complex_list)
ι不睡觉的鱼゛ 2024-09-04 17:40:00
new_list = my_list

因为:new_list 只会引用 my_list,并且在 new_list 中所做的更改也会自动在 my_list 中进行> 反之亦然

有两种简单的方法可以复制列表

new_list = my_list.copy()

new_list = list(my_list)
new_list = my_list

because: new_list will only be a reference to my_list, and changes made in new_list will automatically also be made in my_list and vice versa

There are two easy ways to copy a list

new_list = my_list.copy()

or

new_list = list(my_list)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文