我需要在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]]
这不是我想要的或期望的 . 有人可以解释一下发生了什么,以及如何解决这个问题?
12 回答
通过使用内置列表功能,您可以这样做
让我们以下列方式重写您的代码:
然后,运行以下代码,使一切更清晰 . 代码的作用基本上是打印获取对象的id,其中
并将帮助我们识别它们并分析发生的情况:
您将获得以下输出:
那么现在让我们一步一步走 . 您有
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]]
,原因与第一步相同 .Python容器包含对其他对象的引用 . 看这个例子:
在此
b
是一个列表,其中包含一个项目,该项目是列表a
的引用 . 列表a
是可变的 .列表乘以整数等效于将列表多次添加到自身(请参阅common sequence operations) . 继续这个例子:
我们可以看到列表
c
现在包含对列表a
的两个引用,它相当于c = b * 2
.Python FAQ还包含对此行为的说明:How do I create a multidimensional list?
简单来说,这种情况正在发生,因为在python中一切正常 by reference ,所以当你创建一个列表列表时,你基本上就会遇到这样的问题 .
要解决您的问题,您可以执行以下任一操作:1 . 使用numpy数组documentation for numpy.empty 2.在到达列表时附加列表 . 如果你愿意,你也可以使用字典
试图更具描述性地解释它,
操作1:
操作2:
注意到为什么没有't modifying the first element of the first list didn' t修改每个列表的第二个元素?那是因为
[0] * 2
确实是两个数字的列表,并且无法修改对0的引用 .如果要创建克隆副本,请尝试操作3:
另一种创建克隆副本的有趣方法,操作4:
甚至:
创建一个引用内部
[1,1,1,1]
3次的列表 - 而不是内部列表的三个副本,因此每次修改列表(在任何位置)时,您都会看到三次更改 .它与此示例相同:
它可能有点不那么令人惊讶 .
当你写
[x]*3
时,你基本上得到了列表[x, x, x]
. 也就是说,一个列表中有3个引用相同的x
. 然后,当您修改此单个x
时,通过对它的所有三个引用都可以看到它 .要解决此问题,您需要确保在每个位置创建新列表 . 一种方法是
这将每次重新评估
[1]*4
而不是评估它一次,并对3个列表进行3次引用 .您可能想知道为什么
*
可以't make independent objects the way the list comprehension does. That'因为乘法运算符*
对对象进行操作而不会看到表达式 . 当您使用*
将[[1] * 4]
乘以3时,*
仅查看_元素列表[[1] * 4]
的计算结果,而不是[[1] * 4
表达式文本 .*
不知道如何制作该元素的副本,不知道如何重新评估[[1] * 4]
,也不知道你甚至想要副本,一般来说,甚至可能没有办法复制该元素 .*
唯一的选择是对现有子列表进行新引用,而不是尝试创建新的子列表 . 其他任何事情都会不一致或需要重新设计基础语言设计决策 .相反,列表推导会重新评估每次迭代时的元素表达式 . 每次出于同样的原因
[[1] * 4 for n in range(3)]
重新评估[1] * 4
每次都会重新评估x**2
. 对[1] * 4
的每次评估都会生成一个新列表,因此列表理解可以满足您的需求 .顺便提一下,
[1] * 4
也不会复制[1]
的元素,但是1.value = 2
不会像1.value = 2
那样做,并将1变为2 .我猜大家都在解释发生了什么 . 我建议一种解决方法:
myList = [[1 for i in range(4)] for j in range(3)]
print myList
然后你有:
Live Python Tutor Visualize
除了正确解释问题的接受答案之外,在列表理解中,如果您使用的是python-2.x,请使用
xrange()
返回更高效的生成器(在python 3中range()
执行相同的工作)_
而不是一次性变量n
:此外,作为更多Pythonic方式,您可以使用itertools.repeat()来创建重复元素的迭代器对象:
附:使用numpy,如果你只想创建一个1或0的数组,你可以使用
np.ones
和np.zeros
和/或其他数字使用np.repeat()
:实际上,这正是您所期望的 . 让我们分解这里发生的事情:
你写
这相当于:
这意味着
lst
是一个包含3个元素的列表,所有元素都指向lst1
. 这意味着以下两行是等效的:因为
lst[0]
只不过是lst1
.要获得所需的行为,您可以使用列表理解:
在这种情况下,对每个n重新计算表达式,从而得到不同的列表 .
myList = [[1]*4] * 3
在内存中创建一个列表对象[1,1,1,1]
并将其引用复制3次 . 这相当于obj = [1,1,1,1]; myList = [obj]*3
. 对obj
的任何修改都将反映在三个地方,无论列表中是否引用了obj
. 正确的陈述是:要么
Important thing to note here 是
*
运算符主要用于创建 list of literals . 由于1
是一个文字,因此obj =[1]*4
将创建[1,1,1,1]
,其中每个1
是原子的 not1
的引用重复4次 . 这意味着如果我们做obj[2]=42
,那么obj
将变成[1,1,42,1]
not [42,42,42,42],正如有些人可能认为的那样 .