使用new_list=my_list时,对new_list的任何修改都会每次更改my_list。为什么会出现这种情况,以及如何克隆或复制列表以防止出现这种情况?
当前回答
通过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)
其他回答
框架挑战:对于您的应用程序,您实际上需要复制吗?
我经常看到试图以某种迭代方式修改列表副本的代码。为了构造一个简单的示例,假设我们有非工作(因为不应该修改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成为x的副本,而不是同一列表的名称,这样for循环就会做正确的事情。
但这是错误的做法。从功能上讲,我们真正想做的是在原始列表的基础上创建一个新列表。
我们不需要先做一份拷贝,通常也不应该。
当我们需要对每个元素应用逻辑时
这方面的自然工具是列表理解。这样,我们编写逻辑,告诉我们期望结果中的元素如何与原始元素相关联。它简单、优雅、富有表现力;并且我们避免了在for循环中修改y副本的需要(因为分配给迭代变量不会影响列表-原因与我们首先想要副本的原因相同!)。
对于上面的示例,它看起来像:
x = [8, 6, 7, 5, 3, 0, 9]
y = [element * 2 for element in x]
列表理解非常强大;我们还可以使用它们通过带有if子句的规则过滤掉元素,并且我们可以链接for和if子句(它的工作方式与相应的命令式代码类似,相同的子句的顺序相同;只有最终将在结果列表中结束的值才会移到前面,而不是在“最里面”部分)。如果计划是在修改副本以避免问题的同时迭代原始文件,那么通常有一种更令人愉快的方法来实现这一点,即理解过滤列表。
当我们需要按位置拒绝或插入特定元素时
假设我们有这样的东西
x = [8, 6, 7, 5, 3, 0, 9]
y = x
del y[2:-2] # oops, x was changed inappropriately
我们可以通过将我们不需要的部分放在一起来建立一个列表,而不是先创建一个单独的副本来删除我们不想要的部分。因此:
x = [8, 6, 7, 5, 3, 0, 9]
y = x[:2] + x[-2:]
通过切片处理插入、替换等操作是一项练习。只需说明您希望结果包含哪些子序列。这种情况的一个特殊情况是制作一个反向副本-假设我们需要一个新列表(而不仅仅是反向迭代),我们可以通过切片直接创建它,而不是克隆然后使用.reverse。
这些方法(如列表理解)还有一个优点,即它们将所需的结果创建为表达式,而不是通过程序性地就地修改现有对象(并返回None)。这对于以“流畅”风格编写代码更为方便。
已经有很多答案告诉你如何制作一个正确的副本,但没有一个答案说明为什么你的原始“副本”失败了。
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)
有关复制中的角盒的详细信息,请参阅文档。
所有其他贡献者都给出了很好的答案,当你有一个单一维度(水平化)列表时,这些方法是有效的,但是在目前提到的方法中,只有copy.deepcopy()可以克隆/复制列表,而当你使用多维嵌套列表(列表列表)时,它不会指向嵌套列表对象。虽然菲利克斯·克林在他的回答中提到了这一点,但这个问题还有一点问题,可能还有一个使用内置程序的解决方案,这可能会证明是深度复制的更快替代方案。
虽然new_list=old_list[:],copy.copy(old_list)'和Py3k old_list.copy()适用于单层列表,但它们恢复为指向嵌套在old_list和new_list中的列表对象,对其中一个列表对象的更改将在另一个列表中永久化。
编辑:新信息曝光
正如Aaron Hall和PM 2Ring所指出的那样,使用eval()不仅是一个坏主意,而且比copy.deepcopy()慢得多。这意味着,对于多维列表,唯一的选项是copy.deepcopy()。尽管如此,当您尝试在中等大小的多维数组上使用它时,它确实不是一个选项,因为性能会下降。我尝试使用42x42阵列来计时,这是前所未闻的,甚至对于生物信息学应用程序来说也是如此之大,我放弃了等待响应,只是开始在这篇文章中输入我的编辑。似乎唯一真正的选择就是初始化多个列表并独立处理它们。如果有人对如何处理多维列表复制有任何其他建议,将不胜感激。
正如其他人所说的那样,在多维列表中使用copy模块和copy.devcopy存在严重的性能问题。
Python 3.6计时
下面是使用Python 3.6.8的计时结果。请记住,这些时间是相对的,而不是绝对的。
我坚持只做浅层复制,还添加了一些在Python 2中不可能的新方法,例如list.copy()(Python 3切片的等价物)和两种形式的列表解包(*new_list,=list和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
我们可以看到,Python 2的获胜者仍然表现出色,但并没有远远超过Python 3 list.copy(),特别是考虑到后者的出色可读性。
黑马是拆包和重新包装方法(b=[*a]),它比原始切片快约25%,比其他拆包方法(*b,=a)快两倍多。
b=a*1的表现也出奇地好。
请注意,这些方法不会为列表以外的任何输入输出等效结果。它们都适用于可切片对象,少数适用于任何可迭代对象,但只有copy.copy()适用于更一般的Python对象。
以下是相关方的测试代码(此处的模板):
import timeit
COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'
print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []; for item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a: b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))
菲利克斯已经给出了一个很好的答案,但我想我应该对各种方法进行速度比较:
10.59秒(105.9µs/itn)-copy.depcopy(旧列表)10.16秒(101.6µs/itn)-纯Python Copy()方法使用deepcopy复制类1.488秒(14.88µs/itn)-纯Python Copy()方法不复制类(仅dicts/lists/tuples)0.325秒(3.25µs/itn)-对于old_list:new_list.append(项目)中的项目0.217秒(2.17µs/itn)-[i代表old_list](列表理解)0.186秒(1.86µs/itn)-复制副本(old_list)0.075秒(0.75µs/itn)-列表(旧列表)0.053秒(0.53µs/itn)-新列表=[];新列表扩展(旧列表)0.039秒(0.39µs/itn)-old_list[:](列表切片)
所以最快的是列表切片。但请注意,与copy.deepcopy()和python版本不同,copy.copy()、list[:]和list(list)不会复制列表中的任何列表、字典和类实例,因此如果原始列表发生变化,它们也会在复制的列表中发生变化,反之亦然。
(如果有人感兴趣或想提出任何问题,以下是脚本:)
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
推荐文章
- 证书验证失败:无法获得本地颁发者证书
- 当使用pip3安装包时,“Python中的ssl模块不可用”
- 无法切换Python与pyenv
- Python if not == vs if !=
- 如何从scikit-learn决策树中提取决策规则?
- 为什么在Mac OS X v10.9 (Mavericks)的终端中apt-get功能不起作用?
- 将旋转的xtick标签与各自的xtick对齐
- 为什么元组可以包含可变项?
- 如何合并字典的字典?
- 如何创建类属性?
- 不区分大小写的“in”
- 在Python中获取迭代器中的元素个数
- 解析日期字符串并更改格式
- 使用try和。Python中的if
- 如何在Python中获得所有直接子目录