使用new_list=my_list时,对new_list的任何修改都会每次更改my_list。为什么会出现这种情况,以及如何克隆或复制列表以防止出现这种情况?
当前回答
菲利克斯已经给出了一个很好的答案,但我想我应该对各种方法进行速度比较:
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
其他回答
new_list=my_list实际上并没有创建第二个列表。赋值只是将引用复制到列表,而不是实际的列表,因此new_list和my_list在赋值后都引用相同的列表。
要实际复制列表,您有几个选项:
您可以使用内置的list.copy()方法(从Python 3.3开始提供):new_list=old_list.copy()您可以对其进行切片:new_list=旧列表[:]亚历克斯·马特利(Alex Martelli)(至少在2007年)对此的看法是,这是一种奇怪的语法,永远使用它都没有意义(在他看来,下一篇更具可读性)。您可以使用内置的list()构造函数:new_list=列表(old_list)您可以使用泛型copy.copy():导入副本new_list=复制副本(old_list)这比list()慢一点,因为它必须首先找到old_list的数据类型。如果您还需要复制列表中的元素,请使用genericcopy.deepcopy():导入副本new_list=复制.depcopy(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)]
我想发布一些不同于其他答案的内容。尽管这很可能不是最容易理解或最快的选项,但它提供了深度复制工作方式的一些内部视图,同时也是深度复制的另一种选择。我的函数是否有bug其实并不重要,因为这是为了展示一种复制问题答案之类的对象的方法,同时也是为了解释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]。不可变的数字不能重复,但另一层可以。您可以使用列表理解复制它:[1,2,3]中的x代表x]
现在,假设您有一个列表:[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]
现在,您有了一个函数,它可以使用递归将str、bools、floast、int甚至列表的任何列表深度复制到无限多个层。这就是深度复制。
TLDR:Depcopy使用递归来复制对象,并且只返回与以前相同的不可变对象,因为不可变对象无法复制。然而,它深度复制可变对象的最内层,直到到达对象的最外层。
有人告诉我Python 3.3+添加了list.copy()方法,它应该和切片一样快:
newlist = old_list.copy()
框架挑战:对于您的应用程序,您实际上需要复制吗?
我经常看到试图以某种迭代方式修改列表副本的代码。为了构造一个简单的示例,假设我们有非工作(因为不应该修改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)。这对于以“流畅”风格编写代码更为方便。
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]]]
-----------------------------
推荐文章
- 证书验证失败:无法获得本地颁发者证书
- 当使用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中获得所有直接子目录