我创建了一个列表的列表:

>>> 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程序员;考虑:

>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]

[[]]是一个包含空列表的单元素列表,因此[[]]* 3的所有三个元素都是对这个空列表的引用。修改列表中的任何元素都会修改这个列表。

另一个解释这一点的例子是使用多维数组。

你可能尝试过这样做一个多维数组:

>>> A = [[None] * 2] * 3

如果你打印出来,看起来是正确的:

>>> A
[[None, None], [None, None], [None, None]]

但当你赋值时,它会出现在多个地方:

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

原因是使用*复制列表不会创建副本,它只创建对现有对象的引用。3创建一个包含3个对长度为2的相同列表的引用的列表。对一行的更改将显示在所有行中,这几乎肯定不是您想要的。

其他回答

@spelchekr from Python list乘法:[[…]*3制作了3个列表,这些列表在修改时相互镜像,我有同样的问题 “为什么只有外层的*3会产生更多的参考,而内部的则不会?”为什么不都是1s?”

li = [0] * 3
print([id(v) for v in li])  # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li])  # [140724141863760, 140724141863728, 140724141863728]
print(id(0))  # 140724141863728
print(id(1))  # 140724141863760
print(li)     # [1, 0, 0]

ma = [[0]*3] * 3  # mainly discuss inner & outer *3 here
print([id(li) for li in ma])  # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma])  # [1987013355080, 1987013355080, 1987013355080]
print(ma)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

下面是我在尝试上面的代码后的解释:

内部的*3也创建引用,但它的引用是不可变的,就像[&0,&0,&0],那么当你改变li[0]时,你不能改变const int 0的任何底层引用,所以你可以只改变引用地址为新的&1; 而ma = [&li, &li, &li]且li是可变的,因此当你调用ma[0][0] = 1时,ma[0][0]等于&li[0],因此所有的&li实例将其第一个地址更改为&1。

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

你写

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) ]

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

当你写[x]*3时,你会得到[x, x, x]这个列表。也就是说,一个有3个对同一个x的引用的列表。当你修改这个x时,它通过所有三个对它的引用都是可见的:

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

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

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

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

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


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.

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

相比之下,列表推导式在每次迭代时重新计算元素表达式。[[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。

我正在补充我的答案,以图解方式解释同样的问题。

你创建2D的方式,创建一个浅列表

arr = [[0]*cols]*row

相反,如果您希望更新列表中的元素,则应该使用

rows, cols = (5, 5) 
arr = [[0 for i in range(cols)] for j in range(rows)] 

解释:

可以使用以下命令创建列表:

arr = [0]*N 

or

arr = [0 for i in range(N)] 

在第一种情况下,数组的所有下标都指向同一个整数对象

当你给一个特定的索引赋值时,就会创建一个新的int对象,例如arr[4] = 5

现在让我们看看当我们创建一个list of list时会发生什么,在这种情况下,top list的所有元素都指向同一个列表

如果你更新任何索引的值,就会创建一个新的int对象。但是由于所有顶级列表索引都指向同一个列表,所以所有行看起来都是一样的。您会觉得更新一个元素就是更新该列中的所有元素。

感谢Pranav Devarakonda提供的简单解释

为了更详细地解释它,

操作1:

x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]

x[0][0] = 1
print(x) # [[1, 0], [0, 0]]

操作2:

y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [1, 0]]

注意到为什么修改第一个列表的第一个元素不修改每个列表的第二个元素吗?这是因为[0]* 2实际上是两个数字的列表,对0的引用不能被修改。

如果您想创建克隆副本,请尝试操作3:

import copy
y = [0] * 2   
print(y)   # [0, 0]

y = [y, copy.deepcopy(y)]  
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [0, 0]]

另一种创建克隆副本的有趣方法,操作4:

import copy
y = [0] * 2
print(y) # [0, 0]

y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]

y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]