协慌网

登录 贡献 社区

列表更改列表意外地反映在子列表中

我需要在 Python 中创建列表列表,因此我输入了以下内容:

myList = [[1] * 4] * 3

该列表如下所示:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

然后,我更改了最内在的值之一:

myList[0][0] = 5

现在我的列表如下所示:

[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

这不是我想要或期望的。有人可以解释发生了什么,以及如何解决吗?

答案

当您编写[x]*3您基本上会得到列表[x, x, x] 。即,具有 3 个引用的列表引用了相同的x 。然后,当您修改此单个x ,通过对它的所有三个引用都可以看到它:

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}\n"
    f"id(l[1]): {id(l[1])}\n"
    f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

要解决此问题,您需要确保在每个位置都创建一个新列表。一种方法是

[[1]*4 for _ in range(3)]

它将每次重新评估[1]*4而不是一次评估并且对 1 个列表进行 3 次引用。


您可能想知道为什么*不能像列表理解那样创建独立的对象。这是因为乘法运算符*在对象上运算而没有看到表达式。当使用*到乘法[[1] * 4]由 3, *只看到 1 元素的列表[[1] * 4]的计算结果为,而不是[[1] * 4表达的文本。 *不知道如何制作该元素的副本,也不知道如何重新评估[[1] * 4] ,也不知道您甚至想要复制,而且一般来说,甚至没有办法复制该元素。

*唯一的选择是对现有子列表进行新引用,而不是尝试创建新子列表。其他所有内容都将不一致或需要对基础语言设计决策进行重大重新设计。

相反,列表推导会在每次迭代时重新评估元素表达式。 [[1] * 4 for n in range(3)] ] 每次都会重新评估[1] * 4 [出于相同原因, [x**2 for x in range(3)]重新评估x**2[1] * 4每个评估都将生成一个新列表,因此列表理解功能可以满足您的需求。

顺便说一句, [1] * 4也不会复制[1]的元素,但这并不重要,因为整数是不可变的。您无法执行1.value = 2并将 1 变成 2 的操作。

size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

框架和物体

实时 Python 导师可视化

实际上,这正是您所期望的。让我们分解一下这里发生的事情:

你写

lst = [[1] * 4] * 3

这等效于:

lst1 = [1]*4
lst = [lst1]*3

这意味着lst是一个包含 3 个元素的列表,所有元素都指向lst1 。这意味着以下两行是等效的:

lst[0][0] = 5
lst1[0] = 5

因为lst[0]就是lst1

要获得所需的行为,可以使用列表理解:

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

在这种情况下,将对每个 n 重新计算表达式,从而得出不同的列表。