今天在Python课程学习中遇到了一个二维列表的问题,我通过两个列表的乘法创建一个SIZE*SIZE大小的二维列表,并在对第一行第一列的元素进行赋值时通过调试代码,监视变量窗口发现所有行的第一个元素都被赋值了,以下为代码片段

SIZE = 7
arrary = [[0] * SIZE] * SIZE
row = 0
col = 0
for i in range(SIZE * SIZE + 1)
    arrary[row][col] = i

调试-监视变量

再进行多次单步调试发现,每一次对某个元素赋值,都会将该元素所在列的所有元素都赋上相同的数值。通过问题查询,浏览他人的博客文章发现了有趣的答案,这种情况的出现与Python的引用机制有关,也是在使用列表乘法时可能会遇到的坑。下面来解释一下。

在Python中使用列表乘法可以构建重复的列表元素,比如:

a = [0] * 5
print(a)
# 结果为[0, 0, 0, 0, 0]
 
a[0] = 10
print(a)
# 结果为[10, 0, 0, 0, 0]

b = [[0]] * 5
print(b)
# 结果为[[0], [0], [0], [0], [0]]

b[0][0] = 1
print(b)
# 结果为[[1], [1], [1], [1], [1]]

可以看出,当列表元素为列表时,当修改一个元素,其所在列的所有元素也会发生改变

原因是,Python通过乘法构建重复的列表元素时,对于不可变对象(例如数值类型、字符串)而言是复制值对象,但对于可变对象(例如列表、字典)而言则是复制引用,每一个复制出来的列表元素的引用对象所指向的值对象与被复制的列表元素一致。再举一个字典的例子,如下

c = [{'key': 0}] * 5
c[0]['key'] = 1
# 预期结果为[{'key': 1}, {'key': 0}, {'key': 0}, {'key': 0}, {'key': 0}]
# 实际结果为[{'key': 1}, {'key': 1}, {'key': 1}, {'key': 1}, {'key': 1}]

上面的字典列表,虽然只改变了其中一个字典元素,但整个列表中的所有字典元素值都发生了改变,这是因为这5个字典对象都指向了同一个字典对象的值对象,我们也可以通过布尔表达式判断两个元素的值对象是否一致,代码片段如下

judge = arrary[0][0] is arrary[0][1]
# id(arrary[0][0]) == id(arrary[0][1])
print(judge)
# 结果为True

因此,我们对于包含可变对象的列表慎用列表乘法,可使用列表加法进行重复元素构建

SIZE = 7
array = [[0] * SIZE] * SIZE
for i in range(SIZE - 1):
    array += [[0] * SIZE]
最后修改:2022 年 11 月 10 日