
在Python中,使用乘法运算符 * 来“乘以”一个列表,会创建一个新列表,其中包含对原列表元素的重复引用。理解这一点对于处理可变对象(如列表自身)至关重要。
考虑以下两种情况:
复制不可变对象列表:
>>> a = [0] * 3 >>> a [0, 0, 0] >>> id(a[0]), id(a[1]), id(a[2]) (140733388238040, 140733388238040, 140733388238040) # 示例ID,实际值可能不同
这里,列表 a 的所有元素都引用同一个整数对象 0。由于整数是不可变的,这通常不会引起问题。当你尝试修改 a[0] = 1 时,实际上是将 a[0] 指向了一个新的整数对象 1,而其他元素仍然指向 0。
立即学习“Python免费学习笔记(深入)”;
复制可变对象列表(浅拷贝):
>>> b = [[]] * 3 >>> b [[], [], []] >>> id(b[0]), id(b[1]), id(b[2]) (2856577670848, 2856577670848, 2856577670848) # 示例ID,实际值可能不同
在这种情况下,列表 b 的所有元素都引用同一个空列表对象 []。这意味着 b[0], b[1], b[2] 实际上是同一个列表的三个不同名称。如果修改其中一个:
>>> b[0].append(1) >>> b [[1], [1], [1]]
你会发现所有嵌套列表都受到了影响,因为它们指向的是同一个底层列表对象。这就是所谓的“浅拷贝”:只复制了顶层列表的引用,而没有复制其内部对象。
当创建嵌套列表时,如果不注意 * 运算符的浅拷贝特性,很容易遇到意料之外的行为。让我们以一个具体的矩阵初始化为例:
假设我们有一个用于确定维度的辅助列表 A,例如 A = [[0,0],[0,0],[0,0]],这意味着我们需要一个3行2列的矩阵。
# 假设 A 的维度为 3x2 # len(A) = 3, len(A[0]) = 2 # 步骤1: 创建一个包含 None 的行 empty_row = [None] * len(A[0]) # 此时 empty_row = [None, None] # 并且 empty_row[0] 和 empty_row[1] 都指向同一个 None 对象 # 步骤2: 使用 empty_row 来创建矩阵 empty_matrix = [ empty_row ] * len(A) # 此时 empty_matrix = [[None, None], [None, None], [None, None]] # 关键在于 empty_matrix[0]、empty_matrix[1]、empty_matrix[2] 都指向了同一个 empty_row 列表对象
为了验证这一点,我们可以打印它们的内存地址(ID):
import sys
# 假设 A = [[0,0],[0,0],[0,0]]
A = [[0,0],[0,0],[0,0]]
empty_row = [None] * len(A[0])
empty_matrix = [ empty_row ] * len(A)
print("--- 初始化时的ID ---")
for i in range(len(empty_matrix)):
print(f"Row ID: {id(empty_matrix[i])}")
for j in range(len(empty_matrix[0])):
print(f" Element ID: {id(empty_matrix[i][j])}", end = ", ")
print()示例输出(ID值可能不同):
--- 初始化时的ID ---
Row ID: 2856577670848
Element ID: 140733388238040, Element ID: 140733388238040,
Row ID: 2856577670848
Element ID: 140733388238040, Element ID: 140733388238040,
Row ID: 2856577670848
Element ID: 140733388238040, Element ID: 140733388238040, 从输出可以看出,所有行的ID都是相同的(2856577670848),这证明 empty_matrix 中的所有行都指向了同一个 empty_row 对象。同时,所有元素的ID也都是相同的(140733388238040),因为它们都指向同一个 None 对象。
现在,让我们对这个 empty_matrix 进行赋值操作:
print("\n--- 赋值操作 ---")
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()示例输出:
--- 赋值操作 --- --- 赋值后的矩阵内容 --- 20, 21, 20, 21, 20, 21,
这个输出可能与初学者的预期不符。许多人可能期望得到一个像 0, 1, \n 10, 11, \n 20, 21, 这样的矩阵。然而,实际输出显示所有行都是 20, 21。
这是因为 empty_matrix[i][j] = i*10+j 语句执行的是赋值操作,而不是对原始 None 对象的原地修改。当执行 empty_matrix[i][j] = value 时:
由于 empty_matrix 中的所有行都指向同一个 empty_row 对象,对 empty_matrix[i][j] 的任何赋值操作,实际上都是在修改这个唯一的共享 empty_row。因此,当循环结束后,empty_row 的内容将是循环中最后一次对 empty_row 元素进行的赋值结果,即 i=2, j=0 时的 20 和 i=2, j=1 时的 21。
为了进一步验证,我们可以在赋值后再次打印ID:
print("\n--- 赋值后的ID ---")
for i in range(len(empty_matrix)):
print(f"Row ID: {id(empty_matrix[i])}") # 行ID保持不变
for j in range(len(empty_matrix[0])):
print(f" Element ID: {id(empty_matrix[i][j])}", end = ", ") # 元素ID已改变
print()示例输出(ID值可能不同):
--- 赋值后的ID ---
Row ID: 2856577670848
Element ID: 1782914902928, Element ID: 1782914902960,
Row ID: 2856577670848
Element ID: 1782914902928, Element ID: 1782914902960,
Row ID: 2856577670848
Element ID: 1782914902928, Element ID: 1782914902960, 可以看到,所有行的ID仍然是相同的(2856577670848),这再次确认了 empty_matrix 中的所有行依然指向同一个列表对象。然而,元素ID已经改变(1782914902928 和 1782914902960),这表明在 empty_row 中,原来的 None 对象已经被新的整数对象 20 和 21 替换了。
为了避免这种浅拷贝带来的引用问题,尤其是在需要独立操作每个嵌套列表时,应该使用列表推导式来创建独立的内部列表:
# 假设 A = [[0,0],[0,0],[0,0]]
A = [[0,0],[0,0],[0,0]]
# 使用列表推导式创建独立的嵌套列表
# 外层循环创建 len(A) 个独立的行列表
# 内层循环为每个行列表创建 len(A[0]) 个独立的 None 元素
correct_matrix = [[None for _ in range(len(A[0]))] for _ in range(len(A))]
print("\n--- 正确创建的矩阵的ID ---")
for i in range(len(correct_matrix)):
print(f"Row ID: {id(correct_matrix[i])}")
for j in range(len(correct_matrix[0])):
print(f" Element ID: {id(correct_matrix[i][j])}", end = ", ")
print()示例输出(ID值可能不同):
--- 正确创建的矩阵的ID ---
Row ID: 2856577670848
Element ID: 140733388238040, Element ID: 140733388238040,
Row ID: 2856577670928
Element ID: 140733388238040, Element ID: 140733388238040,
Row ID: 2856577671008
Element ID: 140733388238040, Element ID: 140733388238040, 现在,correct_matrix 中的每一行都有一个独立的ID,这意味着它们是不同的列表对象。对其中一行的修改不会影响其他行。
print("\n--- 对正确创建的矩阵进行赋值操作 ---")
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()示例输出:
--- 对正确创建的矩阵进行赋值操作 --- --- 赋值后的正确矩阵内容 --- 0, 1, 10, 11, 20, 21,
这正是我们期望的结果。
理解Python中变量、对象和引用之间的关系是编写健壮代码的关键。特别是在处理可变数据结构时,对浅拷贝和深拷贝的认识能够有效避免许多潜在的逻辑错误。
以上就是Python列表乘法与引用机制深度解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号