
在python中,使用乘法运算符*来“乘以”列表是一种常见的操作,它可以快速创建一个包含重复元素的列表。然而,当列表中的元素是可变对象时,这种操作会引入一个重要的引用机制,即“浅复制”。
考虑以下代码片段,它尝试创建一个二维矩阵:
# 假设 A 是一个二维列表,例如 A = [[0,0], [0,0], [0,0]]
# len(A[0]) = 2, len(A) = 3
empty_row = [None] * len(A[0]) # 创建一个包含 len(A[0]) 个 None 的列表
empty_matrix = [ empty_row ] * len(A) # 将 empty_row 复制 len(A) 次
print("--- 初始状态下的对象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[{j}]: {id(empty_matrix[i][j])}", end = ", ")
print()运行这段代码,你会观察到类似以下的输出(ID值可能不同):
--- 初始状态下的对象ID ---
行对象ID: 2856577670848
元素ID[0]: 140733388238040, 元素ID[1]: 140733388238040,
行对象ID: 2856577670848
元素ID[0]: 140733388238040, 元素ID[1]: 140733388238040,
行对象ID: 2856577670848
元素ID[0]: 140733388238040, 元素ID[1]: 140733388238040, 从输出中可以清晰地看到:
这种行为是Python列表乘法操作的特性:它创建的是对元素的引用,而不是元素的独立副本。对于不可变对象(如数字、字符串、None),这通常不是问题,因为它们的值不能被修改。但对于可变对象(如列表、字典),这会导致意想不到的副作用。
立即学习“Python免费学习笔记(深入)”;
现在,我们尝试向这个empty_matrix赋值:
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()你可能会预期得到一个像[[0, 1], [10, 11], [20, 21]]这样的矩阵。然而,实际输出却是:
--- 赋值后的矩阵内容 --- 20, 21, 20, 21, 20, 21,
这个结果表明,所有行都变成了[20, 21]。这正是因为所有行都引用了同一个empty_row列表对象。当执行empty_matrix[i][j] = i*10+j时,这是一个赋值操作,它做了以下事情:
由于empty_matrix[0]、empty_matrix[1]和empty_matrix[2]都指向同一个empty_row列表,对其中任何一个索引的修改都会体现在所有引用该列表的行上。最终,empty_row列表的元素被最后一次迭代(即i=2)中的赋值操作所覆盖,变成了[2*10+0, 2*10+1],也就是[20, 21]。
为了进一步验证,我们可以在赋值后再次检查对象ID:
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[{j}]: {id(empty_matrix[i][j])}", end = ", ")
print()输出会是:
--- 赋值后对象ID的验证 ---
行对象ID: 1782995372160
元素ID[0]: 1782914902928, 元素ID[1]: 1782914902960,
行对象ID: 1782995372160
元素ID[0]: 1782914902928, 元素ID[1]: 1782914902960,
行对象ID: 1782995372160
元素ID[0]: 1782914902928, 元素ID[1]: 1782914902960,可以看到,所有行的对象ID仍然相同,这再次确认了它们引用的是同一个列表对象。但现在,该列表中的元素ID已变为1782914902928(对应20)和1782914902960(对应21),它们是不同的整数对象。
要创建包含独立子列表的嵌套列表(即“深复制”效果),应确保每个子列表都是一个全新的对象。以下是两种常用的方法:
列表推导式是Python中创建列表的简洁且高效的方式。通过嵌套使用列表推导式,可以确保每个内部列表都是一个独立的新对象。
# 假设 rows = 3, cols = 2
rows = len(A)
cols = len(A[0])
# 创建一个包含独立子列表的矩阵
independent_matrix = [[None for _ in range(cols)] for _ in range(rows)]
print("\n--- 使用列表推导式创建的矩阵 ---")
for i in range(rows):
print(f"行对象ID: {id(independent_matrix[i])}")
for j in range(cols):
print(f" 元素ID[{j}]: {id(independent_matrix[i][j])}", end = ", ")
print()
# 赋值测试
for i in range(rows):
for j in range(cols):
independent_matrix[i][j] = i*10+j
print("\n--- 赋值后的独立矩阵内容 ---")
for r in independent_matrix:
for c in r:
print(c, end = ", ")
print()输出将是:
--- 使用列表推导式创建的矩阵 ---
行对象ID: 1782995372224
元素ID[0]: 140733388238040, 元素ID[1]: 140733388238040,
行对象ID: 1782995372352
元素ID[0]: 140733388238040, 元素ID[1]: 140733388238040,
行对象ID: 1782995372480
元素ID[0]: 140733388238040, 元素ID[1]: 140733388238040,
--- 赋值后的独立矩阵内容 ---
0, 1,
10, 11,
20, 21, 可以看到,现在每行的对象ID都是不同的,并且赋值操作按预期工作,每行都保持了其独立的数值。
另一种方法是使用传统的for循环,在每次迭代中显式地创建一个新的子列表并添加到主列表中。
# 假设 rows = 3, cols = 2
rows = len(A)
cols = len(A[0])
# 创建一个包含独立子列表的矩阵
independent_matrix_loop = []
for _ in range(rows):
independent_matrix_loop.append([None for _ in range(cols)])
print("\n--- 使用循环创建的矩阵 ---")
for i in range(rows):
print(f"行对象ID: {id(independent_matrix_loop[i])}")
for j in range(cols):
print(f" 元素ID[{j}]: {id(independent_matrix_loop[i][j])}", end = ", ")
print()
# 赋值测试
for i in range(rows):
for j in range(cols):
independent_matrix_loop[i][j] = i*10+j
print("\n--- 赋值后的独立矩阵内容 (循环创建) ---")
for r in independent_matrix_loop:
for c in r:
print(c, end = ", ")
print()这种方法也会产生与列表推导式相同的结果,因为每次append操作都添加了一个新创建的列表对象。
通过深入理解Python的引用机制和赋值操作的本质,开发者可以更有效地管理数据结构,编写出健壮且可预测的代码。
以上就是Python列表乘法与引用:深度解析嵌套结构中的预期与实际行为的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号