列表列表更改意外地反映在子列表中
我创建了一个列表列表:
>>> xs = [[1] * 4] * 3
>>> print(xs)
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
然后,我更改了最里面的一个值:
>>> xs[0][0] = 5
>>> print(xs)
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
为什么每个子列表的每个第一个元素都更改为 5
?
另请参阅:
如何在 Python 中初始化二维数组? 获取问题的解决方法
如何在 Python 中初始化空列表字典? 对于列表字典的类似问题
I created a list of lists:
>>> xs = [[1] * 4] * 3
>>> print(xs)
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Then, I changed one of the innermost values:
>>> xs[0][0] = 5
>>> print(xs)
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
Why did every first element of each sublist change to 5
?
See also:
How do I clone a list so that it doesn't change unexpectedly after assignment? and
How to initialize a two-dimensional array in Python? for workarounds for the problem
List of dictionary stores only last appended value in every iteration for an analogous problem with a list of dicts
How do I initialize a dictionary of empty lists in Python? for an analogous problem with a dict of lists
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
当您编写
[x]*3
时,您实质上会得到列表[x, x, x]
。 也就是说,一个列表包含 3 个对同一x
的引用。 然后,当您修改此单个x
时,它可以通过对它的所有三个引用可见:要修复它,您需要确保在每个位置创建一个新列表。 一种方法是
每次重新评估
[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。When you write
[x]*3
you get, essentially, the list[x, x, x]
. That is, a list with 3 references to the samex
. When you then modify this singlex
it is visible via all three references to it:To fix it, you need to make sure that you create a new list at each position. One way to do it is
which will reevaluate
[1]*4
each time instead of evaluating it once and making 3 references to 1 list.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.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)]
reevaluatesx**2
every time. Every evaluation of[1] * 4
generates a new list, so the list comprehension does what you wanted.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 like1.value = 2
and turn a 1 into a 2.使用 Python Tutor 进行实时可视化:
Live visualization using Python Tutor:
事实上,这正是您所期望的。 让我们分解一下这里发生的事情:
你这样写
这相当于:
这意味着
lst
是一个包含 3 个元素的列表,全部指向lst1
。 这意味着以下两行是等效的:因为
lst[0]
只不过是lst1
。要获得所需的行为,您可以使用列表理解:
在这种情况下,将针对每个
n
重新计算表达式,从而生成不同的列表。Actually, this is exactly what you would expect. Let's decompose what is happening here:
You write
This is equivalent to:
This means
lst
is a list with 3 elements all pointing tolst1
. This means the two following lines are equivalent:As
lst[0]
is nothing butlst1
.To obtain the desired behavior, you can use a list comprehension:
In this case, the expression is re-evaluated for each
n
, leading to a different list.甚至:
创建一个引用内部
[1,1,1,1]
3 次的列表 - 不是内部列表的三个副本,因此任何时候您修改列表(在任何位置),您将看到三次更改。它与这个例子是一样的:
它可能不那么令人惊讶。
or even:
Creates a list that references the internal
[1,1,1,1]
3 times - not three copies of the inner list, so any time you modify the list (in any position), you'll see the change three times.It's the same as this example:
where it's probably a little less surprising.
my_list = [[1]*4] * 3
在内存中创建一个列表对象[1,1,1,1]
,并复制其引用 3 次。 这相当于 obj = [1,1,1,1]; my_list = [obj]*3. 对obj
的任何修改都将反映在列表中引用obj
的三个位置。正确的说法是:
或者
这里要注意的重要事情是
*
运算符主要用于创建文字列表。 尽管1
是不可变的,obj = [1]*4
仍会创建重复 4 次的1
列表以形成[ 1,1,1,1]
。 但是,如果对不可变对象进行任何引用,该对象就会被新对象覆盖。这意味着如果我们执行
obj[1] = 42
,那么obj
将变为[1,42,1,1]
正如某些人可能假设的那样。 这也可以验证:[42,42,42,42]
my_list = [[1]*4] * 3
creates one list object[1,1,1,1]
in memory and copies its reference 3 times over. This is equivalent toobj = [1,1,1,1]; my_list = [obj]*3
. Any modification toobj
will be reflected at three places, whereverobj
is referenced in the list.The right statement would be:
or
Important thing to note here is that the
*
operator is mostly used to create a list of literals. Although1
is immutable,obj = [1]*4
will still create a list of1
repeated 4 times over to form[1,1,1,1]
. But if any reference to an immutable object is made, the object is overwritten with a new one.This means if we do
obj[1] = 42
, thenobj
will become[1,42,1,1]
notas some may assume. This can also be verified:[42,42,42,42]
除了正确解释问题的接受答案之外,不要使用以下代码创建包含重复元素的列表:
此外,您可以使用
itertools.repeat()
创建重复元素的迭代器对象:PS 如果您使用 NumPy 并且只想创建一个您可以使用的一或零数组
np.ones
和
np.zeros
和/或对于其他数字,请使用np .重复
:Alongside the accepted answer that explained the problem correctly, instead of creating a list with duplicated elements using following code:
Also, you can use
itertools.repeat()
to create an iterator object of repeated elements:P.S. If you're using NumPy and you only want to create an array of ones or zeroes you can use
np.ones
andnp.zeros
and/or for other numbers usenp.repeat
:Python 容器包含对其他对象的引用。 请参阅此示例:
在此
b
中,是一个列表,其中包含一项对列表a
的引用。 列表a
是可变的。列表乘以整数相当于多次将列表与自身相加(请参阅 常见序列操作)。 继续这个例子:
我们可以看到列表
c
现在包含两个对列表a
的引用,这相当于c = b * 2
。Python FAQ 还包含对此行为的解释: How do I create a multiDimensional列表?
Python containers contain references to other objects. See this example:
In this
b
is a list that contains one item that is a reference to lista
. The lista
is mutable.The multiplication of a list by an integer is equivalent to adding the list to itself multiple times (see common sequence operations). So continuing with the example:
We can see that the list
c
now contains two references to lista
which is equivalent toc = b * 2
.Python FAQ also contains explanation of this behavior: How do I create a multidimensional list?
我添加我的答案以图解方式解释相同的内容。
创建 2D 的方式创建了一个浅列表
相反,如果您想更新列表的元素,您应该使用
解释:
可以使用以下命令创建列表:
或
在第一种情况下,所有数组的索引指向同一个整数对象
当您为特定索引分配值时,会创建一个新的 int 对象,例如
arr[4] = 5
创建现在让我们看看当我们创建一个列表列表时会发生什么,在这种情况下,顶部列表的所有元素都将指向同一个列表
并且如果更新任何索引的值,将创建一个新的 int 对象。 但由于所有顶级列表索引都指向同一个列表,因此所有行看起来都一样。 你会感觉更新一个元素就是更新该列中的所有元素。
鸣谢:感谢Pranav Devarakonda 的简单解释这里< /a>
I am adding my answer to explain the same diagrammatically.
The way you created the 2D, creates a shallow list
Instead, if you want to update the elements of the list, you should use
Explanation:
One can create a list using:
or
In the first case all the indices of the array point to the same integer object
and when you assign a value to a particular index, a new int object is created, for example
arr[4] = 5
createsNow let us see what happens when we create a list of list, in this case, all the elements of our top list will point to the same list
And if you update the value of any index a new int object will be created. But since all the top-level list indexes are pointing at the same list, all the rows will look the same. And you will get the feeling that updating an element is updating all the elements in that column.
Credits: Thanks to Pranav Devarakonda for the easy explanation here
让我们按以下方式重写您的代码:
然后运行以下代码以使一切更加清晰。 代码的作用基本上是打印
id
s,其中,将帮助我们识别它们并分析发生的情况:
您将得到以下输出:
现在让我们一步一步进行。 您有
x
(即1
),以及包含x
的单个元素列表y
。 你的第一步是y * 4
,它将为你提供一个新的列表z
,它基本上是[x, x, x, x]
,即它创建一个新列表,其中包含 4 个元素,这些元素是对初始x
对象的引用。 下一步非常相似。 您基本上执行z * 3
,即[[x, x, x, x]] * 3
并返回[[x, x, x, x ], [x, x, x, x], [x, x, x, x]]
,原因与第一步相同。Let's rewrite your code in the following way:
Then having this, run the following code to make everything more clear. What the code does is basically print the
id
s of the obtained objects, whichand will help us identify them and analyse what happens:
And you will get the following output:
So now let's go step-by-step. You have
x
which is1
, and a single element listy
containingx
. Your first step isy * 4
which will get you a new listz
, which is basically[x, x, x, x]
, i.e. it creates a new list which will have 4 elements, which are references to the initialx
object. The next step is pretty similar. You basically doz * 3
, which is[[x, x, x, x]] * 3
and returns[[x, x, x, x], [x, x, x, x], [x, x, x, x]]
, for the same reason as for the first step.简单来说,发生这种情况是因为在 python 中,一切都通过引用工作,所以当你创建一个列表列表时,你基本上会遇到这样的问题。
要解决您的问题,您可以执行以下任一操作:
In simple words this is happening because in python everything works by reference, so when you create a list of list that way you basically end up with such problems.
To solve your issue you can do either one of them:
每个人都在解释发生了什么。 我会建议一种解决方法:
然后你会得到:
Everyone is explaining what is happening. I'll suggest one way to solve it:
And then you get:
@spelchekr 来自 Python 列表乘法:[ [...]]*3 制作了 3 个列表,修改后彼此镜像,我也有同样的问题
“为什么只有外部
*3
会创建更多引用,而内部却不会?为什么不都是 1?”这是我在尝试上面的代码后的解释:
*3
也会创建引用,但它的引用是不可变的,类似于[&0, &0, &0]< /code>,那么当你改变
li[0]
时,你无法改变const int0
的任何底层引用,所以你只需将引用地址改为新的&1
;ma = [&li, &li, &li]
和li
是可变的,所以当你调用ma[0][0] = 1
,ma[0][0]
等于&li[0]
,所以所有的&li
实例会将其第一个地址更改为&1
。@spelchekr from Python list multiplication: [[...]]*3 makes 3 lists which mirror each other when modified and I had the same question about
"Why does only the outer
*3
create more references while the inner one doesn't? Why isn't it all 1s?"Here is my explanation after trying the code above:
*3
also creates references, but its references are immutable, something like[&0, &0, &0]
, then when you changeli[0]
, you can't change any underlying reference of const int0
, so you can just change the reference address into the new one&1
;ma = [&li, &li, &li]
andli
is mutable, so when you callma[0][0] = 1
,ma[0][0]
is equal to&li[0]
, so all the&li
instances will change its 1st address into&1
.尝试更描述性地解释它,
操作 1:
操作 2:
注意到为什么修改第一个列表的第一个元素没有修改每个列表的第二个元素? 这是因为
[0] * 2
实际上是两个数字的列表,并且无法修改对 0 的引用。如果您想创建克隆副本,请尝试操作 3:
创建克隆副本的另一种有趣方法,操作 4:
Trying to explain it more descriptively,
Operation 1:
Operation 2:
Noticed why doesn't modifying the first element of the first list didn't modify the second element of each list? That's because
[0] * 2
really is a list of two numbers, and a reference to 0 cannot be modified.If you want to create clone copies, try Operation 3:
another interesting way to create clone copies, Operation 4:
通过使用内置列表功能,您可以这样做
By using the inbuilt list function you can do like this
来自官方文档:
From official documentation:
我来到这里是因为我想看看如何嵌套任意数量的列表。 上面有很多解释和具体示例,但是您可以使用以下递归函数概括 N 维列表的列表的列表...:
您第一次调用该函数,如下所示:
where
(3, 5,2)
是结构维度的元组(类似于 numpyshape
参数),1.0
是您希望结构成为的元素初始化为(也适用于 None)。 请注意, init 参数仅由递归调用提供,以继承上面的嵌套子列表输出:
设置特定元素:
结果输出:
上面演示了列表的非类型化本质
I arrived here because I was looking to see how I could nest an arbitrary number of lists. There are a lot of explanations and specific examples above, but you can generalize N dimensional list of lists of lists of ... with the following recursive function:
You make your first call to the function like this:
where
(3,5,2)
is a tuple of the dimensions of the structure (similar to numpyshape
argument), and1.0
is the element you want the structure to be initialized with (works with None as well). Note that theinit
argument is only provided by the recursive call to carry forward the nested child listsoutput of above:
set specific elements:
resulting output:
the non-typed nature of lists is demonstrated above
虽然最初的问题使用乘法运算符构造了子列表,但我将添加一个使用相同子列表列表的示例。 为了完整性添加这个答案,因为这个问题经常被用作该问题的规范。
每个字典值中的列表都是同一个对象,尝试更改其中一个字典值将在所有中看到。
构建字典的正确方法是为每个值使用列表的副本。
While the original question constructed the sublists with the multiplication operator, I'll add an example that uses the same list for the sublists. Adding this answer for completeness as this question is often used as a canonical for the issue
The list in each dictionary value is the same object, trying to change one of the dictionaries values will be seen in all.
The correct way to construct the dictionary would be to use a copy of the list for each value.
请注意,序列中的项目不会被复制; 它们被多次引用。 这常常困扰着新的 Python 程序员; 考虑:
发生的情况是
[[]]
是一个包含空列表的单元素列表,因此[[]] * 3
的所有三个元素都是对这个空列表。 修改列表的任何元素都会修改该单个列表。解释这一点的另一个例子是使用多维数组。
您可能尝试创建一个像这样的多维数组:
如果打印它,这看起来是正确的:
但是当您分配一个值时,它会出现在多个位置:
原因是使用
*
复制列表 不创建副本,它只创建对现有对象的引用。 3 创建一个列表,其中包含对长度为 2 的同一列表的 3 个引用。 对一行的更改将显示在所有行中,这几乎肯定不是您想要的。Note that items in the sequence are not copied; they are referenced multiple times. This often haunts new Python programmers; consider:
What has happened is that
[[]]
is a one-element list containing an empty list, so all three elements of[[]] * 3
are references to this single empty list. Modifying any of the elements of lists modifies this single list.Another example to explain this is using multi-dimensional arrays.
You probably tried to make a multidimensional array like this:
This looks correct if you print it:
But when you assign a value, it shows up in multiple places:
The reason is that replicating a list with
*
doesn’t create copies, it only creates references to the existing objects. The 3 creates a list containing 3 references to the same list of length two. Changes to one row will show in all rows, which is almost certainly not what you want.每个子列表的每个第一个元素都更改为 5,因为“xs”具有相同的列表 3 次,数据正在共享。 打印不显示这一点,但使用:
https://pypi.org/project/memory-graph/
您可以绘制数据图表并轻松查看:
相反,您可能希望使用列表理解,以便不共享数据:
给出输出:
全面披露:我是 memory_graph 的开发者。
Every first element of each sublist changed to 5 because 'xs' has the same list three times, the data is being shared. Printing doesn't show this but using:
https://pypi.org/project/memory-graph/
you can graph your data and see this easily:
Instead you probably want to use a list comprehension so that the data is not shared:
Which gives output:
Full disclosure: I am the developer of memory_graph.