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

How do I clone a list so that it doesn't change unexpectedly after assignment?

提问人:aF. 提问时间:4/10/2010 最后编辑:wjandreaaF. 更新时间:11/12/2023 访问量:2148428

问:

使用时,每次对 的任何修改都会更改。为什么会这样,我该如何克隆或复制列表以防止它?new_list = my_listnew_listmy_list

python 列表 克隆 可变

评论

5赞 Andrew 11/15/2021
new_list = my_list只需将名称分配给所引用的对象。new_listmy_list
9赞 Bharel 1/22/2022
请参阅 Python 常见问题解答
1赞 Karl Knechtel 6/4/2022
Смотритетакже: stackoverflow.com/questions/240178
1赞 jdhao 6/6/2022
相关新闻: 这篇文章这个

答:

54赞 Paul Tarjan 4/10/2010 #1

thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 
37赞 erisco 4/10/2010 #2

Python 这样做的成语是newList = oldList[:]

4042赞 Felix Kling 4/10/2010 #3

new_list = my_list实际上不会创建第二个列表。赋值只是复制对列表的引用,而不是实际列表,因此在赋值后两者都引用同一个列表。new_listmy_list

若要实际复制列表,有以下几个选项:

  • 您可以使用内置的 list.copy() 方法(从 Python 3.3 开始可用):

    new_list = old_list.copy()
    
  • 你可以切片它:

    new_list = old_list[:]
    

    Alex Martelli 对此的看法(至少在 2007 年是这样)是,这是一种奇怪的语法,使用它没有任何意义。;)(在他看来,下一个更具可读性)。

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

    new_list = list(old_list)
    
  • 您可以使用通用的 copy.copy():

    import copy
    new_list = copy.copy(old_list)
    

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

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

    import copy
    new_list = copy.deepcopy(old_list)
    

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

例:

import copy

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

    def __repr__(self):
        return 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)]

评论

6赞 moojen 12/22/2020
正如@Georgy在下面的答案中正确指出的那样,对new_list值的任何更改也会更改my_list中的值。所以实际上 copy.deepcopy() 方法是唯一没有引用原始列表及其值的真实副本。
0赞 wjandrea 8/16/2022
@moojen 如果只包含不可变的对象,那就不是问题了。my_list
0赞 moojen 8/17/2022
@wjandrea 为什么要复制不可变对象列表?
2赞 wjandrea 8/17/2022
@moojen 因为列表本身是可变的,而新赋值只创建一个引用。例如x = []; y = x; y.append(1); x -> [1]
755赞 cryo 4/10/2010 #4

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

  1. 10.59秒(105.9μs/ITN) - copy.deepcopy(old_list)
  2. 10.16 秒 (101.6 μs/itn) - 使用 deepcopy 复制类的纯 Python 方法Copy()
  3. 1.488 秒 (14.88 μs/itn) - 纯 Python 方法不复制类(仅字典/列表/元组)Copy()
  4. 0.325秒(3.25μs/ITN)-for item in old_list: new_list.append(item)
  5. 0.217 秒 (2.17 μs/ITN) - (列表理解[i for i in old_list])
  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[:])

所以最快的是列表切片。但请注意,与 和 python 版本不同,与 python 版本不同,它不会复制列表中的任何列表、字典和类实例,因此如果原始版本发生变化,它们也会在复制的列表中发生变化,反之亦然。copy.copy()list[:]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

评论

0赞 uuu777 5/5/2021
这是否意味着追加和列表推导是最佳选择?
3赞 wjandrea 8/16/2022
这些数字可能已经过时。我尝试在我的 PC 上运行脚本的修改版本(甚至很旧),所有数字都明显较低。值得注意的是,根据我的统计,列表理解速度快了 4 倍以上。
1赞 Hacker 9/7/2022
什么是用的?list.copy()
1赞 Karl Knechtel 10/15/2022
我没有研究过;但我的猜测是,3.x 中对本地查找的更改,除了使列表推导式获得自己的范围的更改之外,还可以将迭代变量的查找编译到中,这会导致相对加速。LOAD_FAST
1赞 Karl Knechtel 3/9/2023
River的回答给出了更多当前的时间:stackoverflow.com/a/43220129
183赞 anatoly techtonik 7/23/2013 #5

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

newlist = old_list.copy()

评论

14赞 CyberMew 9/26/2018
是的,根据文档 docs.python.org/3/library/stdtypes.html#mutable-sequence-types,创建一个浅拷贝(与 相同)。s.copy()ss[:]
4赞 loved.by.Jesus 4/24/2020
实际上,目前看来,,,比切片略快。请参阅下面的@AaronsHall答案。python3.8.copy()
0赞 ShadowRanger 12/1/2020
@loved.by.Jesus:是的,他们在 3.7 中添加了对 Python 级别方法调用的优化,这些优化在 3.8 中被 PEP 590 扩展到 C 扩展方法调用,消除了每次调用方法时创建绑定方法的开销,因此现在调用的成本是对类型进行查找,然后是一个相对便宜的无参数函数调用,最终调用与切片相同的内容。切片仍然需要构建一个对象,然后通过类型检查和解包来做同样的事情。alist.copy()dictlistslice
3赞 ShadowRanger 12/1/2020
当然,他们正在努力优化常量切片的重复构建,因此在 3.10 中,切片可能会再次获胜。不过,这一切都毫无意义;渐近性能是相同的,固定开销相对较小,因此使用哪种方法并不重要。
0赞 mtraceur 11/3/2022
@ShadowRanger在这里所说的一切都是重要原则的一个很好的例子:语义没有性能,只有实现才有(如果两种不同方式的语义是相同的,并且性能足够重要,最终优化将尽可能有效地完成这两者 - 但更简单的语义更容易优化,因为它们需要处理的边缘情况更少, 因此,如果您只使用以最直接的方式说明您意图的操作,您通常会更快地获得最佳优化。
158赞 Russia Must Remove Putin 10/25/2014 #6

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

在 Python 3 中,可以使用以下命令进行浅拷贝:

a_copy = a_list.copy()

在 Python 2 和 3 中,您可以获得一个包含原始完整切片的浅拷贝:

a_copy = a_list[:]

解释

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

浅表复制

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

在 Python 2 和 3 中有不同的方法可以做到这一点。Python 2 方法也将在 Python 3 中工作。

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

创建另一个指针不会创建副本

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

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

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

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

深拷贝

要创建列表的深度副本,在 Python 2 或 3 中,请在 copy 模块中使用 deepcopy

import copy
a_deep_copy = copy.deepcopy(a_list)

为了演示这如何允许我们制作新的子列表:

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

因此,我们看到深度复制的列表与原始列表完全不同。您可以滚动自己的函数 - 但不要。您可能会创建使用标准库的 deepcopy 函数不会出现的错误。

请勿使用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

评论

2赞 John Locke 1/10/2019
如果列表是 2D 的,则不需要深层副本。如果它是列表列表,并且这些列表内部没有列表,则可以使用 for 循环。目前,我正在使用,而且速度要快得多。list_copy=[]for item in list: list_copy.append(copy(item))
68赞 jack 11/24/2014 #7

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

Python 不会将值存储在变量中;它将名称绑定到对象。您的原始赋值采用了 所引用的对象,并将其绑定到该对象。无论您使用哪个名称,仍然只有一个列表,因此在将其称为 时所做的更改将在将其称为 时保留。此问题的其他每个答案都为您提供了创建要绑定到的新对象的不同方法。my_listnew_listmy_listnew_listnew_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)

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

22赞 AMR 7/10/2015 #8

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

当 和 对于 Py3k 处理单级列表时,它们会恢复为指向嵌套在 和 中的对象,并且对其中一个对象的更改将在另一个对象中永久存在。new_list = old_list[:]copy.copy(old_list)'old_list.copy()listold_listnew_listlist

编辑:新信息曝光

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

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

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

正如其他人所说,使用该模块和多维列表存在重大性能问题。copycopy.deepcopy

评论

5赞 PM 2Ring 7/10/2015
这并不总是有效,因为不能保证返回的字符串足以重新创建对象。此外,是最后的手段;有关详细信息,请参阅 SO 老将 Ned Batchelder 的 Eval 确实很危险。因此,当您提倡使用时,您真的应该提到它可能很危险。repr()eval()eval()
1赞 AMR 7/11/2015
公平点。尽管我认为 Batchelder 的观点是,在 Python 中拥有该函数通常是一种风险。与其说你是否在代码中使用了这个函数,不如说它本身就是 Python 中的一个安全漏洞。我的示例没有将它与从 、 甚至文本文件接收输入的函数一起使用。它更像是初始化一次空白的多维列表,然后只是在循环中复制它,而不是在循环的每次迭代时重新初始化。eval()input()sys.agrv
1赞 AMR 7/11/2015
正如 @AaronHall 所指出的,使用可能存在严重的性能问题,因此除了这是一个坏主意之外,它可能也太慢而无法工作。new_list = eval(repr(old_list))
47赞 River 4/5/2017 #9

Python 3.6 计时

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

我坚持只做浅拷贝,还添加了一些在 Python 2 中不可能的新方法,例如(相当于 Python 3 切片)和两种形式的列表解包( 和 ):list.copy()*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()

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

b = a * 1也出乎意料地好。

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


以下是相关方的测试代码(模板来自此处):

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

评论

2赞 SuperShoot 3/2/2020
可以在 3.8 上确认类似的故事 - 一种明显的方法;b=[*a]
1赞 ekhumoro 11/22/2020
在复制这些小列表时,其中一些时间比较并不是特别有意义。使用一系列列表长度(包括一些非常大的列表长度)进行测试会提供更多信息。
1赞 Peter Mortensen 5/12/2021
计时数字应四舍五入到适当数量的有效数字。15 位有效数字没有任何意义。
0赞 River 5/18/2021
我基本上只是将计时代码的原始输出粘贴到此处。似乎你的抱怨更多的是关于时间它如何显示时间,我几乎无法控制。
0赞 Karl Knechtel 7/6/2022
该方法在 2.x 中真的不可能吗?a * 1
15赞 Ravi Shankar 6/27/2017 #10
new_list = my_list[:]

new_list = my_list

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

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

执行此操作的其他方法是:

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

评论

0赞 2pichar 2/15/2022
从技术上讲,是一个浅层的副本。深度复制列表的唯一方法是使用my_list[:]copy.deepcopy()
13赞 jainashish 11/1/2017 #11

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

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

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

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
79赞 Aaditya Ura 11/13/2017 #12

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

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

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

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

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

copy = list_1

现在,如果您认为复制复制了list_1,那么您就错了。该函数可以向我们显示两个变量是否可以指向同一个对象。让我们试试这个:id

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

输出为:

4329485320
4329485320

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

所以我们知道,Python 不会在变量中存储任何东西,变量只是引用对象,对象存储值。这里对象是一个,但我们通过两个不同的变量名称创建了对同一对象的两个引用。这意味着两个变量都指向同一个对象,只是名称不同。list

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

Enter image description here

在图像中,list_1copy 是两个变量名称,但这两个变量的对象是相同的,即 .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,这意味着两个变量都指向不同的对象。所以这里实际发生的事情是:

Enter image description here

现在让我们尝试修改列表,看看我们是否仍然面临前面的问题:

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

Enter image description here

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

解决方案是什么?解决方案是功能。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)

Enter image description here

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

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

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

它输出:

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

正如你所看到的,它没有修改原来的嵌套列表,它只是修改了复制的列表。

17赞 SCB 2/26/2018 #13

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

您可以使用“splat operator”执行列表解包: ,这也将复制列表的元素。*

old_list = [1, 2, 3]

new_list = [*old_list]

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

这种方法的明显缺点是它仅在 Python 3.5+ 中可用。

不过,从时间上讲,这似乎比其他常用方法表现得更好。

x = [random.random() for _ in range(1000)]

%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]

%timeit a = [*x]

#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

评论

1赞 not2qubit 9/24/2018
此方法在修改副本时如何表现?
2赞 SCB 9/25/2018
@not2qubit是指追加或编辑新列表的元素。在示例中,是两个不同的列表,编辑一个不会更改另一个(除非您直接更改元素本身(例如列表列表),否则这些方法都不是深拷贝)。old_listnew_list
9赞 Chris_Rands 5/16/2018 #14

请注意,在某些情况下,如果您已经定义了自己的自定义类并且想要保留属性,那么您应该使用 or 而不是替代方案,例如在 Python 3 中:copy.copy()copy.deepcopy()

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
13赞 Corman 9/8/2019 #15

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

任何深度拷贝功能的核心都是制作浅层拷贝的方法。 如何?简单。任何深度复制函数都只复制不可变对象的容器。深度复制嵌套列表时,您只是复制外部列表,而不是复制列表内部的可变对象。您只是在复制容器。这同样适用于课程。深度复制类时,会深度复制其所有可变属性。那么,如何呢?为什么只需要复制容器,如列表、字典、元组、迭代器、类和类实例?

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

4赞 B.Mr.W. 11/24/2019 #16

通过 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) 
5赞 Dr. Hippo 2/22/2020 #17

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

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

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

1赞 shahar_m 4/11/2020 #18

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

评论

0赞 Jean-François Fabre 11/19/2020
DeepCopy 必须仅在需要时使用,并且应该了解它的真正作用。
2赞 Roshin Raphel 6/4/2020 #19

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

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

您应该使用 copy 模块通过以下方式创建新列表

import copy
new_list = copy.deepcopy(my_list)
2赞 Laurent Lyaudet 7/3/2020 #20

还有另一种方法可以复制直到现在才列出的列表:添加一个空列表:.l2 = l + []

我用 Python 3.8 测试了它:

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

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

评论

1赞 Karl Knechtel 7/6/2022
这有效,在我的测试中,与较长列表的最快选项一样快,并且仅比短列表稍差。b = [*a]
1赞 fjemi 1/31/2021 #21

使用的方法取决于要复制的列表的内容。如果列表包含嵌套,则 deepcopy 是唯一有效的方法,否则答案中列出的大多数方法(slice、loop [for]、copy、extend、combine 或 unpack)将在相似的时间内工作和执行(循环和 deepcopy 除外,它们最坏)。dicts

脚本

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}
]}]
-1赞 Karl Knechtel 9/24/2022 #22

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

我经常看到尝试以某种迭代方式修改列表副本的代码。为了构造一个简单的示例,假设我们有非工作(因为不应该修改)的代码,例如: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]

自然而然地,人们会问如何使 成为 的副本,而不是同一个列表的名称,以便循环会做正确的事情。yxfor

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

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

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

这方面的自然工具是列表理解。这样,我们编写的逻辑告诉我们所需结果中的元素如何与原始元素相关。它简单、优雅、富有表现力;而且,我们避免了在循环中修改副本的变通方法(因为分配给迭代变量不会影响列表 - 原因与我们最初想要副本的原因相同!yfor

对于上面的示例,它如下所示:

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

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

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

相反,假设我们有类似的东西

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


这些方法(如列表推导)也具有这样的优点,即它们以表达式的形式创建所需的结果,而不是通过就地程序性地修改现有对象(并返回 None)。这对于以“流畅”风格编写代码更方便。

-1赞 Fellipe Sanches 10/15/2022 #23

每种复印模式的简短说明:

浅拷贝构造一个新的复合对象,然后(在可能的情况下)插入对原始对象中的对象的引用 - 创建一个浅拷贝

new_list = my_list

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

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)

评论

0赞 Karl Knechtel 10/15/2022
多个现有的答案已经解释了如何使用复制,以及每个答案的作用以及如何选择。我没有看到这里添加了什么。listcopy.deepcopy
0赞 Fellipe Sanches 10/15/2022
@KarlKnechtel一个简单明了的答案......
-1赞 Salindaw 2/17/2023 #24
new_list = my_list

因为:new_list仅是对my_list的引用,在new_list中所做的更改也将自动在my_list中进行,反之亦然

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

new_list = my_list.copy()

new_list = list(my_list)