
在python中,当使用乘法运算符*来“复制”一个包含可变对象的列表时,例如创建嵌套列表,一个常见的误解是它会生成完全独立的副本。然而,*操作符实际上创建的是对原始对象的多个引用,而非独立的深层副本。这意味着所有“复制”出来的元素都指向内存中的同一个可变对象。
让我们通过一个示例来验证这一点。假设我们要创建一个3x2的矩阵,并用None填充。
# 假设 A 是一个用于确定维度的列表,例如 A = [[0,0],[0,0],[0,0]]
# 这里的 A 仅用于获取维度,实际内容不影响示例
rows = 3
cols = 2
empty_row = [None] * cols # 创建一个包含两个None的列表
empty_matrix = [empty_row] * rows # 将 empty_row 引用三次
print("--- 初始状态下的对象ID ---")
for i in range(len(empty_matrix)):
print(f"行 {i} 的对象ID: {id(empty_matrix[i])}")
for j in range(len(empty_matrix[0])):
print(f" 元素 ({i},{j}) 的对象ID: {id(empty_matrix[i][j])}", end = ", ")
print()运行上述代码,你会发现所有行的对象ID都是相同的,这表明empty_matrix中的所有元素都引用了同一个empty_row列表对象。同时,empty_row中的所有None元素也指向同一个None对象(None是不可变单例)。
示例输出可能如下(ID值会因运行环境而异):
--- 初始状态下的对象ID ---
行 0 的对象ID: 2856577670848
元素 (0,0) 的对象ID: 140733388238040, 元素 (0,1) 的对象ID: 140733388238040,
行 1 的对象ID: 2856577670848
元素 (1,0) 的对象ID: 140733388238040, 元素 (1,1) 的对象ID: 140733388238040,
行 2 的对象ID: 2856577670848
元素 (2,0) 的对象ID: 140733388238040, 元素 (2,1) 的对象ID: 140733388238040, 这清晰地表明,empty_matrix[0]、empty_matrix[1]和empty_matrix[2]都指向了同一个列表对象。
立即学习“Python免费学习笔记(深入)”;
现在,我们尝试向这个“矩阵”的每个元素赋值。
# 继续上面的 empty_matrix
# A 维度不变,假设仍为 3x2
rows = 3
cols = 2
for i in range(rows):
for j in range(cols):
empty_matrix[i][j] = i * 10 + j # 对元素进行赋值
print("\n--- 赋值后的矩阵内容 ---")
for r in empty_matrix:
for c in r:
print(c, end = ", ")
print()
print("\n--- 赋值后各对象ID ---")
for i in range(len(empty_matrix)):
print(f"行 {i} 的对象ID: {id(empty_matrix[i])}")
for j in range(len(empty_matrix[0])):
print(f" 元素 ({i},{j}) 的对象ID: {id(empty_matrix[i][j])}", end = ", ")
print()你可能会预期输出是:
0, 1, 10, 11, 20, 21,
然而,实际输出却是:
--- 赋值后的矩阵内容 --- 20, 21, 20, 21, 20, 21,
为什么会这样?这是因为 empty_matrix[i][j] = value 这样的赋值操作,实际上是让 empty_matrix[i] 这个列表中的第 j 个位置的引用指向了一个新的 value 对象,而不是修改了原先被引用的对象。
由于 empty_matrix 中的所有行(empty_matrix[0], empty_matrix[1], empty_matrix[2])都指向了同一个列表对象,当我们在循环中执行 empty_matrix[i][j] = i * 10 + j 时,我们实际上是在反复修改同一个列表对象的元素。每次循环迭代都会更新这个共享列表的元素。因此,当所有赋值操作完成后,这个共享列表的元素将是最后一次迭代(即 i=2)所赋的值。
例如,当 i=0, j=0 时,empty_matrix[0][0] = 0 会将共享列表的第一个元素从 None 变为 0。 当 i=1, j=0 时,empty_matrix[1][0] = 10 会将共享列表的第一个元素从 0 变为 10。 当 i=2, j=0 时,empty_matrix[2][0] = 20 会将共享列表的第一个元素从 10 变为 20。 同理,共享列表的第二个元素最终会变为 21。
所以,最终所有行都显示 [20, 21]。
再观察赋值后的对象ID:
--- 赋值后各对象ID ---
行 0 的对象ID: 1782995372160
元素 (0,0) 的对象ID: 1782914902928, 元素 (0,1) 的对象ID: 1782914902960,
行 1 的对象ID: 1782995372160
元素 (1,0) 的对象ID: 1782914902928, 元素 (1,1) 的对象ID: 1782914902960,
行 2 的对象ID: 1782995372160
元素 (2,0) 的对象ID: 1782914902928, 元素 (2,1) 的对象ID: 1782914902960, 你会发现:
要创建包含独立列表的嵌套列表(即真正的二维矩阵),每行都应该是一个独立的列表对象。最常见和推荐的方法是使用列表推导式:
rows = 3
cols = 2
# 方法一:使用列表推导式
# 每次循环都会创建一个新的列表对象
matrix_correct = [[None for _ in range(cols)] for _ in range(rows)]
print("--- 正确创建的矩阵 (列表推导式) ---")
for i in range(rows):
print(f"行 {i} 的对象ID: {id(matrix_correct[i])}")
for j in range(cols):
print(f" 元素 ({i},{j}) 的对象ID: {id(matrix_correct[i][j])}", end = ", ")
print()
# 进行赋值操作
for i in range(rows):
for j in range(cols):
matrix_correct[i][j] = i * 10 + j
print("\n--- 赋值后的正确矩阵内容 ---")
for r in matrix_correct:
for c in r:
print(c, end = ", ")
print()
print("\n--- 赋值后正确矩阵的各对象ID ---")
for i in range(rows):
print(f"行 {i} 的对象ID: {id(matrix_correct[i])}")
for j in range(cols):
print(f" 元素 ({i},{j}) 的对象ID: {id(matrix_correct[i][j])}", end = ", ")
print()运行这段代码,你会看到每行的ID都是不同的,证明它们是独立的列表对象。赋值后,输出将符合预期:
--- 赋值后的正确矩阵内容 --- 0, 1, 10, 11, 20, 21,
此时,matrix_correct[0][0]、matrix_correct[1][0]、matrix_correct[2][0] 将分别指向整数对象 0、10、20,它们是不同的对象。
另一种使用循环创建独立嵌套列表的方法:
# 方法二:使用循环
matrix_loop = []
for _ in range(rows):
matrix_loop.append([None] * cols) # 每次循环都创建一个新的列表对象并添加到 matrix_loop这种方法与列表推导式达到相同的效果,即每行都是一个独立的列表对象。
通过深入理解Python的引用机制和赋值操作的本质,可以有效避免在处理复杂数据结构时遇到的常见陷阱,编写出更健壮、可预测的代码。
以上就是Python中列表乘法与引用陷阱:深入理解可变对象行为的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号