Python中二维列表不同初始化方式行为差异的原因是什么?
为什么用
[[0]*3]*3初始化二维列表会出现"牵一发动全身"的问题? 这问题太常见啦!很多刚接触Python列表的朋友都会踩这个坑,我来给你掰扯清楚背后的逻辑~
先再明确下你遇到的两种现象:
- 用嵌套列表推导式
square = [[0 for i in range(3)] for j in range(3)]初始化后,修改square[0][0] = 1,只有第一个子列表的第一个元素变成1,这是符合预期的正常行为。 - 但用
square = [[0] * 3] * 3初始化时,修改square[0][0] = 1后,三个子列表的第一个元素全变成了1,就像所有子列表是"绑定"在一起的一样。
核心原因:引用复用 vs 独立创建
问题出在[[0]*3] *3的执行逻辑上:
- 首先
[0] * 3确实会创建一个新列表[0,0,0],这一步没问题。 - 但紧接着的
*3,并不是把这个子列表复制三份,而是把这个子列表的内存引用重复添加了三次到外层列表中。也就是说,外层列表的三个元素,指向的是内存里同一个列表对象!
所以当你修改square[0][0]时,本质上是在修改那个唯一的子列表对象,而外层列表里的三个元素都是这个对象的引用,自然看起来三个子列表都变了。
而嵌套列表推导式[[0 for i in range(3)] for j in range(3)]就不一样了:外层的for j in range(3)每循环一次,都会执行一次内层的[0 for i in range(3)],每次都会创建一个全新的子列表。三个子列表是完全独立的内存对象,修改其中一个自然不会影响另外两个。
直观验证:用id()看内存地址
你可以用Python的id()函数来直观验证这个结论,它会返回对象的内存地址:
# 嵌套列表推导式创建的二维列表 square1 = [[0 for i in range(3)] for j in range(3)] print(id(square1[0]), id(square1[1]), id(square1[2])) # 输出三个不同的内存地址,说明是三个独立对象 # 用*3复用引用的二维列表 square2 = [[0]*3]*3 print(id(square2[0]), id(square2[1]), id(square2[2])) # 输出三个完全相同的内存地址,说明指向同一个对象
正确的二维列表初始化方式
如果要创建包含独立子列表的二维列表,除了嵌套列表推导式,还可以用循环逐个添加:
square = [] for _ in range(3): # 每次循环都创建一个新的子列表 square.append([0] * 3)
这样初始化后的二维列表,修改任意子列表的元素都不会影响其他子列表。
内容的提问来源于stack exchange,提问作者Antoine Guenet
相关产品推荐
相关产品推荐

