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

>>> 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:对于字典列表的类似问题,字典列表只存储每次迭代中最后追加的值

如何初始化一个字典,其值是不同的空列表?对于列表字典的类似问题


当前回答

每个人都在解释发生了什么。我将提出一种解决方法:

my_list = [[1 for i in range(4)] for j in range(3)]

my_list[0][0] = 5
print(my_list)

然后你得到:

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

其他回答

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

你创建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提供的简单解释

虽然最初的问题使用乘法运算符构造子列表,但我将添加一个示例,该示例对子列表使用相同的列表。添加这个答案是为了完整性,因为这个问题经常被用作问题的规范

node_count = 4
colors = [0,1,2,3]
sol_dict = {node:colors for node in range(0,node_count)}

列表中的每个字典值都是同一个对象,试图改变其中一个字典值就会看到全部。

>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> [v is colors for v in sol_dict.values()]
[True, True, True, True]
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 2, 3], 2: [0, 2, 3], 3: [0, 2, 3]}

构造字典的正确方法是为每个值使用列表的副本。

>>> colors = [0,1,2,3]
>>> sol_dict = {node:colors[:] for node in range(0,node_count)}
>>> sol_dict
{0: [0, 1, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}
>>> sol_dict[0].remove(1)
>>> sol_dict
{0: [0, 2, 3], 1: [0, 1, 2, 3], 2: [0, 1, 2, 3], 3: [0, 1, 2, 3]}

为了更详细地解释它,

操作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]]

Python容器包含对其他对象的引用。请看这个例子:

>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

其中b是一个列表,其中包含一个对列表a的引用。列表a是可变的。

将列表与整数相乘相当于将列表与自身相加多次(请参阅常用序列操作)。继续这个例子:

>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

我们可以看到,列表c现在包含两个对列表a的引用,这等价于c = b * 2。

Python常见问题还包含对这种行为的解释:如何创建多维列表?

让我们按照以下方式重写代码:

x = 1
y = [x]
z = y * 4

my_list = [z] * 3

有了这些,运行下面的代码使一切更清楚。代码所做的基本上是打印所获得的对象的id,这

返回一个对象的“标识符”

并将帮助我们识别它们并分析发生了什么:

print("my_list:")
for i, sub_list in enumerate(my_list):
    print("\t[{}]: {}".format(i, id(sub_list)))
    for j, elem in enumerate(sub_list):
        print("\t\t[{}]: {}".format(j, id(elem)))

您将得到以下输出:

x: 1
y: [1]
z: [1, 1, 1, 1]
my_list:
    [0]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [1]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [2]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528

现在让我们一步一步来。你有x,它是1,和一个包含x的元素列表y。你的第一步是y * 4,它会得到一个新的列表z,基本上是[x, x, x, x],也就是说,它创建了一个新的列表,它将有4个元素,它们是对初始x对象的引用。下一步非常相似。基本上是z * 3,即[[x, x, x]] * 3,并返回[[x, x, x], [x, x, x], [x, x, x]],与第一步的原因相同。