Python 二维列表只修改一个位置,所有行却一起变化
列表重复运算保留对象引用,导致多行共用一个行列表。用推导式逐行创建,并按单元格是否可变决定内层结构。
初始化三行两列后,只写入第一行,其他行的同一列也变了:
matrix = [[0] * 2] * 3
matrix[0][0] = 9
print(matrix) # 预期 [[9, 0], [9, 0], [9, 0]]
这是 Python 内置列表重复运算产生的引用共享。外层 * 3 没有创建三份行列表,而是让三个位置指向同一个行对象。本文针对 Python 3 的内置列表,不涉及 NumPy 数组的复制和视图规则。
先检查行是不是同一个对象
matrix = [[0] * 2] * 3
print(matrix[0] is matrix[1]) # 预期 True
== 只能说明内容相等,不能判断是否共享同一行;这里需要 is。多个行入口指向同一行对象,所以都能看到这次修改。
内层 [0] * 2 也重复引用,但整数不可变。给某个位置赋值会替换该位置的引用,不会修改整数对象本身。外层共享的对象则是可变列表,所以通过任意一个行入口修改它,都能从其他入口看到变化。
每次循环创建一行
rows, columns = 3, 2
matrix = [[0] * columns for _ in range(rows)]
matrix[0][0] = 9
print(matrix) # 预期 [[9, 0], [0, 0], [0, 0]]
print(matrix[0] is matrix[1]) # 预期 False
列表推导式每轮都会重新求值 [0] * columns,因此创建不同的行。不要先在循环外创建 row,再用 [row for _ in range(rows)];那只是再次保存同一个对象。
如果每个单元格本身也是列表或字典,内层也需要独立创建:
cells = [[[] for _ in range(2)] for _ in range(3)]
cells[0][0].append("ready")
print(cells[0][1]) # 预期 []
print(cells[1][0]) # 预期 []
写成 [[[]] * 2 for _ in range(3)] 虽然能分开行,仍会让同一行内的两个单元格共享列表。应按照实际需要独立修改的层级创建对象。
已有数据要分清复制哪一层
matrix.copy() 或 matrix[:] 只复制外层列表,已有行引用仍被保留,不能解决行共享。若行里都是整数等不可变值,可以逐行复制:
independent = [row.copy() for row in matrix]
这只断开行列表的共享;如果行内还有可变对象,浅拷贝仍共享这些对象,需要按数据结构决定复制深度。不要直接把整个矩阵交给 deepcopy 就认定每个重复引用都变独立:深拷贝会记住已经复制的对象,原结构中的共享关系可能仍被保留。
如果矩阵已经被误改,复制只能复制当前内容,无法推断每行原来应是什么。优先从原始输入重建,再替换初始化代码;保留旧数据以便核对,避免在已污染的矩阵上继续计算。
修复后同时检查两类结果:修改一个单元格时其他行不变,修改同一行的一个可变单元格时相邻单元格也不变。内容暂时相同很正常,关键是需要独立修改的对象不再共享。


