列表在赋值后意外更改这是为什么?我如何预防?

t9aqgxwy  于 2021-09-29  发布在  Java
关注(0)|答案(8)|浏览(259)

使用时 new_list = my_list ,对 new_list 变化 my_list 每一次。这是为什么?我如何克隆或复制列表以防止它?

zc0qhyus

zc0qhyus1#

felix已经提供了一个很好的答案,但我想我应该对各种方法进行速度比较:
10.59秒(105.9µs/itn)- copy.deepcopy(old_list) 10.16秒(101.6µs/itn)-纯python Copy() 方法使用deepcopy复制类
1.488秒(14.88µs/itn)-纯python Copy() 方法不复制类(仅DICT/列表/元组)
0.325秒(3.25µs/itn)- for item in old_list: new_list.append(item) 0.217秒(2.17µs/itn)- [i for i in old_list] (一份清单)
0.186秒(1.86µs/itn)- copy.copy(old_list) 0.075秒(0.75µs/itn)- list(old_list) 0.053秒(0.53µs/itn)- new_list = []; new_list.extend(old_list) 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
cld4siwp

cld4siwp2#

我听说Python3.3+添加了 list.copy() 方法,其速度应与切片速度相同:

newlist = old_list.copy()
elcex8rz

elcex8rz3#

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

在python 3中,可以通过以下方式创建浅拷贝:

a_copy = a_list.copy()

在Python2和Python3中,您可以获得一个带有原始文件完整切片的浅拷贝:

a_copy = a_list[:]

解释

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

浅表副本

浅表副本仅复制列表本身,列表本身是对列表中对象的引用的容器。如果包含的对象本身是可变的,并且其中一个对象发生了更改,那么更改将反映在两个列表中。
在Python2和Python3中有不同的方法来实现这一点。Python2的方法也适用于Python3。

python 2

在python 2中,制作列表的浅拷贝的惯用方法是使用原始列表的完整片段:

a_copy = a_list[:]

您也可以通过将列表传递给列表构造函数来完成相同的任务,

a_copy = list(a_list)

但使用构造函数的效率较低:

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

python 3

在Python3中,列表得到 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 你不是在复制,你只是在内存中添加另一个指向原始列表的名字。当我们复制列表时,可能会遇到类似的问题。

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

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

深拷贝

在Python2或Python3中,要制作列表的深度副本,请使用 deepcopycopy 模块:

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

您可能会看到这被用作deepcopy的一种方式,但不要这样做:

problematic_deep_copy = eval(repr(a_list))

这是危险的,尤其是当你从一个你不信任的来源评估某件事情时。
如果您要复制的子元素没有可以被评估以重现等效元素的表示,那么这是不可靠的。
它的性能也比较差。
在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
wwwo4jvm

wwwo4jvm4#

已经有很多答案告诉你如何制作一个合适的副本,但没有一个能说明你的原始“副本”失败的原因。
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)

有关复制中的角案例的更多信息,请参阅文档。

acruukt9

acruukt95#

让我们从头开始,探讨这个问题。
那么让我们假设您有两个列表:

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

我们必须复制两个列表,现在从第一个列表开始:
首先,让我们尝试设置变量 copy 在我们原来的名单上, list_1 :

copy = list_1

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

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

输出为:

4329485320
4329485320

两个变量都是完全相同的参数。你感到惊讶吗?
所以我们知道,python不在变量中存储任何内容,变量只是引用对象,对象存储值。这里的对象是一个 list 但是我们用两个不同的变量名创建了对同一对象的两个引用。这意味着两个变量都指向同一个对象,只是名称不同。
当你这样做的时候 copy = list_1 ,它实际上在做:

这里在图像列表_1和copy中有两个变量名,但两个变量的对象相同,即 list .
因此,如果您尝试修改复制的列表,那么它也会修改原始列表,因为该列表只有一个,您将修改该列表,无论您是从复制的列表还是从原始列表:

copy[0] = "modify"

print(copy)
print(list_1)

输出:

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

因此,它修改了原始列表:
现在让我们转到复制列表的pythonic方法。

copy_1 = list_1[:]

此方法修复了我们遇到的第一个问题:

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

4338792136
4338791432

所以我们可以看到我们的两个列表都有不同的id,这意味着两个变量都指向不同的对象。所以这里实际发生的是:

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

copy_1[0] = "modify"

print(list_1)
print(copy_1)

输出为:

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

如您所见,它只修改了复制的列表。这意味着它成功了。
你认为我们结束了吗?不,让我们试着复制嵌套列表。

copy_2 = list_2[:]
``` `list_2` 应引用另一个作为副本的对象 `list_2` . 让我们检查一下:

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

我们得到的结果是:

4330403592 4330403528

现在我们可以假设两个列表指向不同的对象,那么现在让我们尝试修改它,看看它给出了我们想要的:

copy_2[0][1] = "modify"

print(list_2, copy_2)

这为我们提供了输出:

'01', 'modify''01', 'modify'

这似乎有点让人困惑,因为我们以前使用的方法是有效的。让我们试着理解这一点。
当您这样做时:

copy_2 = list_2[:]

您只是复制外部列表,而不是内部列表。我们可以使用 `id` 函数再次检查此项。

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

输出为:

4329485832
4329485832

当我们这样做的时候 `copy_2 = list_2[:]` ,这种情况会发生:
![](https://i.stack.imgur.com/3hPti.jpg)
它创建列表的副本,但仅创建外部列表副本,而不创建嵌套列表副本。这两个变量的嵌套列表是相同的,因此,如果尝试修改嵌套列表,那么它也会修改原始列表,因为两个列表的嵌套列表对象是相同的。
解决办法是什么?解决办法是 `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)` 实际发生的情况:
![](https://i.stack.imgur.com/O7yoo.jpg)
这两个嵌套列表指向不同的对象,它们现在有嵌套列表的单独副本。
现在,让我们尝试修改嵌套列表,看看它是否解决了上一个问题:

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

它输出:

'01', '98''01', 'modify'

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

wfveoks06#

使用 thing[:] ```

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

zlhcx6iw

zlhcx6iw7#

python 3.6计时

下面是使用python 3.6.8的计时结果。请记住,这些时间是相对的,而不是绝对的。
我坚持只做浅显的复印

rryofs0p

rryofs0p8#

具有 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 'Foo({!r})'.format(self.val)

foo = Foo(1)

a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)

# edit orignal list and instance

a.append('baz')
foo.val = 5

print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
      % (a, b, c, d, e, f))

结果:

original: ['foo', Foo(5), 'baz']
list.copy(): ['foo', Foo(5)]
slice: ['foo', Foo(5)]
list(): ['foo', Foo(5)]
copy: ['foo', Foo(5)]
deepcopy: ['foo', Foo(1)]

相关问题