
列表乘法 * 的引用机制
当使用 [item] * n 创建列表时,python实际上创建了一个包含 n 个对 item 对象引用的新列表。这意味着所有 n 个元素都指向内存中的同一个对象。对于不可变对象(如整数、字符串、元组),这通常不会引起问题,因为对元素的修改总是创建新对象并重新赋值引用。但对于可变对象(如列表、字典),这种浅层引用机制会导致意外行为。
示例:观察初始引用
假设我们有一个二维数据结构 A,我们希望创建一个与 A 同尺寸的空矩阵 empty_matrix。
# 假设A是一个3x2的矩阵,仅用于确定维度
A = [[0, 0], [0, 0], [0, 0]]
# 创建一个包含两个None的列表作为行模板
empty_row = [None] * len(A[0]) # 结果:[None, None]
# 使用empty_row创建3个重复的行
empty_matrix = [ empty_row ] * len(A) # 结果:[[None, None], [None, None], [None, None]]
print("--- 初始引用ID ---")
for i in range(len(empty_matrix)):
# 打印每行列表对象的ID
print(f"行对象ID: {id(empty_matrix[i])}")
for j in range(len(empty_matrix[0])):
# 打印每行中元素对象的ID
print(f" 元素对象ID: {id(empty_matrix[i][j])}", end = ", ")
print()输出分析:
--- 初始引用ID ---
行对象ID: 2856577670848 # 示例ID,实际值会不同
元素对象ID: 140733388238040, 元素对象ID: 140733388238040,
行对象ID: 2856577670848
元素对象ID: 140733388238040, 元素对象ID: 140733388238040,
行对象ID: 2856577670848
元素对象ID: 140733388238040, 元素对象ID: 140733388238040, 从输出可以看出,empty_matrix 中的所有行(empty_matrix[i])都具有相同的ID,这明确表明它们都指向内存中的同一个列表对象 empty_row。同样,empty_row 中的所有元素(None)也指向同一个 None 对象。
立即学习“Python免费学习笔记(深入)”;
赋值操作对引用的影响
当对列表的某个元素执行赋值操作(例如 list[index] = new_value)时,Python会改变 list[index] 所存储的引用,使其指向 new_value 对象。这并不会修改 index 位置原先指向的对象,而是断开了原有的引用关系,建立了一个新的引用关系。
示例:赋值后的行为
现在,我们尝试为 empty_matrix 的每个元素赋值:
# 假设A的维度与之前相同,例如3x2
# empty_matrix 仍然是 [[None, None], [None, None], [None, None]],所有行和元素共享引用
for i in range(len(A)): # 遍历行
for j in range(len(A[0])): # 遍历列
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"行对象ID: {id(empty_matrix[i])}")
for j in range(len(empty_matrix[0])):
print(f" 元素对象ID: {id(empty_matrix[i][j])}", end = ", ")
print()输出分析:
--- 赋值后的矩阵内容 ---
20, 21,
20, 21,
20, 21,
--- 赋值后的引用ID ---
行对象ID: 1782995372160 # 示例ID,与初始行ID相同
元素对象ID: 1782914902928, 元素对象ID: 1782914902960,
行对象ID: 1782995372160
元素对象ID: 1782914902928, 元素对象ID: 1782914902960,
行对象ID: 1782995372160
元素对象ID: 1782914902928, 元素对象ID: 1782914902960, 为何结果是 20, 21, 20, 21, 20, 21 而不是预期的 0, 1, 10, 11, 20, 21?
- 行引用不变: empty_matrix 中的所有行仍然指向同一个列表对象(即 empty_row 的原始实例)。从赋值后的 id() 输出中,我们可以看到所有行的ID仍然相同。这意味着对任何 empty_matrix[i] 的修改都会反映在所有行中。
- 元素引用改变: 当执行 empty_matrix[i][j] = i*10+j 时,我们改变的是 empty_matrix[i](即那唯一的 empty_row 实例)中索引 j 处的引用。它不再指向 None,而是指向了一个新的整数对象 i*10+j。
- 由于所有行都共享同一个内部列表对象,所以对 empty_matrix[0][j] 的赋值实际上修改了所有行共享的那个列表的第 j 个元素。当循环进行到 i=2 时,empty_matrix[2][0] 被赋值为 20,empty_matrix[2][1] 被赋值为 21。由于所有行都引用同一个底层列表,因此所有行都显示为 20, 21。
- id() 输出也证实了这一点:虽然行ID保持不变,但行内的元素ID在赋值后已经发生了变化,并且不同列的元素ID也不同了,这说明它们现在指向了不同的整数对象。
如何正确创建独立的嵌套列表
要创建具有独立行的嵌套列表(或矩阵),确保每行都是一个独立的列表对象是关键。最常见且推荐的方法是使用列表推导式,它会为每次迭代生成一个新的列表对象。
A = [[0, 0], [0, 0], [0, 0]] # 3x2 矩阵
# 使用列表推导式创建独立的行
# 每次循环都会生成一个新的 [None] * len(A[0]) 列表对象
correct_matrix = [[None] * len(A[0]) for _ in range(len(A))]
print("\n--- 正确创建的矩阵 (初始引用ID) ---")
for i in range(len(correct_matrix)):
print(f"行对象ID: {id(correct_matrix[i])}") # 观察:行ID将不同
for j in range(len(correct_matrix[0])):
print(f" 元素对象ID: {id(correct_matrix[i][j])}", end = ", ")
print()
# 赋值操作
for i in range(len(A)):
for j in range(len(A[0])):
correct_matrix[i][j] = i*10+j
print("\n--- 正确赋值后的矩阵内容 ---")
for r in correct_matrix:
for c in r:
print(c, end = ", ")
print()
print("\n--- 正确赋值后的引用ID ---")
for i in range(len(correct_matrix)):
print(f"行对象ID: {id(correct_matrix[i])}") # 观察:行ID依然不同
for j in range(len(correct_matrix[0])):
print(f" 元素对象ID: {id(correct_matrix[i][j])}", end = ", ")
print()预期输出 (正确行为):
--- 正确创建的矩阵 (初始引用ID) ---
行对象ID: 140733388238040 # 示例ID,与下一行不同
元素对象ID: 140733388238040, 元素对象ID: 140733388238040,
行对象ID: 140733388238120 # 示例










