使用new_list=my_list时,对new_list的任何修改都会每次更改my_list。为什么会出现这种情况,以及如何克隆或复制列表以防止出现这种情况?


当前回答

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

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

您应该使用复制模块创建新列表

import copy
new_list = copy.deepcopy(my_list)

其他回答

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

newlist = old_list.copy()

使用对象[:]

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

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

您可以使用“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)

框架挑战:对于您的应用程序,您实际上需要复制吗?

我经常看到试图以某种迭代方式修改列表副本的代码。为了构造一个简单的示例,假设我们有非工作(因为不应该修改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)。这对于以“流畅”风格编写代码更为方便。

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

剧本

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