DEBUG.WIKI
丽萨主机;为跨境业务选好 IP;原生 / 住宅 IP · 按套餐选择;查看 IP →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

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 就认定每个重复引用都变独立:深拷贝会记住已经复制的对象,原结构中的共享关系可能仍被保留。

如果矩阵已经被误改,复制只能复制当前内容,无法推断每行原来应是什么。优先从原始输入重建,再替换初始化代码;保留旧数据以便核对,避免在已污染的矩阵上继续计算。

修复后同时检查两类结果:修改一个单元格时其他行不变,修改同一行的一个可变单元格时相邻单元格也不变。内容暂时相同很正常,关键是需要独立修改的对象不再共享。