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

2022-01-29 00:00:00 python list nested-lists mutable

问题描述

我需要在 Python 中创建一个列表列表,所以我输入了以下内容:

I needed to create a list of lists in Python, so I typed the following:

my_list = [[1] * 4] * 3

列表如下所示:

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

然后我更改了最里面的值之一:

Then I changed one of the innermost values:

my_list[0][0] = 5

现在我的列表如下所示:

Now my list looks like this:

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

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

which is not what I wanted or expected. Can someone please explain what's going on, and how to get around it?


解决方案

当你编写 [x]*3 时,你得到的,本质上是一个列表 [x, x, x].也就是说,一个包含 3 个对同一 x 的引用的列表.然后,当您修改此单个 x 时,它可以通过对它的所有三个引用可见:

When you write [x]*3 you get, essentially, the list [x, x, x]. That is, a list with 3 references to the same x. When you then modify this single x it is visible via all three references to it:

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}
"
    f"id(l[1]): {id(l[1])}
"
    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]]

要修复它,您需要确保在每个位置创建一个新列表.一种方法是

To fix it, you need to make sure that you create a new list at each position. One way to do it is

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

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

which will reevaluate [1]*4 each time instead of evaluating it once and making 3 references to 1 list.

您可能想知道为什么 * 不能像列表推导式那样生成独立对象.这是因为乘法运算符 * 对对象进行操作,而不会看到表达式.当您使用 *[[1] * 4] 乘以 3 时,* 只能看到 1 元素列表 [[1] * 4] 计算结果为,而不是 [[1] * 4 表达式文本.* 不知道如何制作该元素的副本,不知道如何重新评估 [[1] * 4],甚至不知道您甚至想要副本,一般来说,甚至可能无法复制元素.

You might wonder why * can't make independent objects the way the list comprehension does. That's because the multiplication operator * operates on objects, without seeing expressions. When you use * to multiply [[1] * 4] by 3, * only sees the 1-element list [[1] * 4] evaluates to, not the [[1] * 4 expression text. * has no idea how to make copies of that element, no idea how to reevaluate [[1] * 4], and no idea you even want copies, and in general, there might not even be a way to copy the element.

* 的唯一选择是对现有子列表进行新的引用,而不是尝试创建新的子列表.其他任何事情都会不一致或需要对基本语言设计决策进行重大重新设计.

The only option * has is to make new references to the existing sublist instead of trying to make new sublists. Anything else would be inconsistent or require major redesigning of fundamental language design decisions.

相比之下,列表推导式在每次迭代时重新评估元素表达式.[[1] * 4 for n in range(3)] 每次重新评估 [1] * 4 都是出于同样的原因 [x**2 for xin range(3)] 每次都重新评估 x**2.[1] * 4 的每次求值都会生成一个新列表,因此列表推导式可以满足您的要求.

In contrast, a list comprehension reevaluates the element expression on every iteration. [[1] * 4 for n in range(3)] reevaluates [1] * 4 every time for the same reason [x**2 for x in range(3)] reevaluates x**2 every time. Every evaluation of [1] * 4 generates a new list, so the list comprehension does what you wanted.

顺便说一句,[1] * 4 也不会复制 [1] 的元素,但这没关系,因为整数是不可变的.你不能做像 1.value = 2 这样的事情,然后把 1 变成 2.

Incidentally, [1] * 4 also doesn't copy the elements of [1], but that doesn't matter, since integers are immutable. You can't do something like 1.value = 2 and turn a 1 into a 2.

相关文章