意外反映在子列表中的列表更改列表 [英] List of lists changes reflected across sublists unexpectedly

查看:31
本文介绍了意外反映在子列表中的列表更改列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要在 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.

这篇关于意外反映在子列表中的列表更改列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆