如何克隆一个列表以避免赋值后意外改变?

3296 投票
24 回答
2181177 浏览
提问于 2025-04-15 21:27

当你使用 new_list = my_list 时,每次你对 new_list 进行修改,my_list 也会跟着改变。这是为什么呢?其实这是因为 new_listmy_list 指向的是同一个列表。也就是说,它们共享同一块内存空间。为了避免这种情况,你需要克隆或者复制这个列表,这样就可以让 new_listmy_list 各自独立,互不影响。

24 个回答

183

我听说Python 3.3及以上版本增加了一个叫做 list.copy() 的方法,这个方法的速度应该和用切片一样快:

newlist = old_list.copy()
761

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

  1. 10.59秒(每次105.9微秒) - copy.deepcopy(old_list)
  2. 10.16秒(每次101.6微秒) - 纯Python的 Copy() 方法,使用深拷贝复制类
  3. 1.488秒(每次14.88微秒) - 纯Python的 Copy() 方法,不复制类(只复制字典/列表/元组)
  4. 0.325秒(每次3.25微秒) - for item in old_list: new_list.append(item)
  5. 0.217秒(每次2.17微秒) - [i for i in old_list](这是一种列表推导式
  6. 0.186秒(每次1.86微秒) - copy.copy(old_list)
  7. 0.075秒(每次0.75微秒) - list(old_list)
  8. 0.053秒(每次0.53微秒) - new_list = []; new_list.extend(old_list)
  9. 0.039秒(每次0.39微秒) - 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
4071

new_list = my_list 实际上并没有创建一个新的列表。这个赋值只是复制了对列表的引用,而不是实际的列表内容,所以在赋值后,new_listmy_list 都指向同一个列表。

如果你想真正复制一个列表,有几种方法可以选择:

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

    new_list = old_list.copy()
    
  • 你可以用切片来复制:

    new_list = old_list[:]
    

    Alex Martelli 的看法(至少在 2007年)是,这种写法很奇怪,根本没必要用它。 ;) (在他看来,下面的方法更容易理解)。

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

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

    import copy
    new_list = copy.copy(old_list)
    

    这个方法比 list() 稍慢,因为它需要先确定 old_list 的数据类型。

  • 如果你需要连列表里的元素也一起复制,可以使用通用的 copy.deepcopy() 方法:

    import copy
    new_list = copy.deepcopy(old_list)
    

    显然这是最慢且最占内存的方法,但有时候是不可避免的。这个方法是递归的,可以处理任意层级的嵌套列表(或其他容器)。

示例:

import copy

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

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

撰写回答