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

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

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


当前回答

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

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]],与第一步的原因相同。

其他回答

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

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

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常见问题还包含对这种行为的解释:如何创建多维列表?

注意,序列中的项不会被复制;它们被多次引用。这经常困扰着新的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。

[[1] * 4] * 3

甚至:

[[1, 1, 1, 1]] * 3

创建一个3次引用内部[1,1,1,1]的列表——而不是内部列表的3个副本,因此任何时候修改列表(在任何位置),您都会看到3次更改。

和下面这个例子一样:

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

在那里可能不那么令人惊讶。