Python列表初始化的"陷阱":重复引用的坑

Python列表初始化的"陷阱":重复引用的坑

# 当时的"聪明"代码
matrix = [[0] * 3] * 3  # 创建3x3矩阵,看起来很优雅对吧?

# 修改第一个元素
matrix[0][0] = 1
print(matrix)
# [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
# 卧槽!为什么都变了?

这个例子展示了Python中列表初始化时一个非常经典的"坑",看似优雅的代码却产生了意外结果。让我们来深入分析:

问题现象

当我们用[[0] * 3] * 3创建矩阵并修改其中一个元素时,所有子列表的对应位置都被修改了:

matrix = [[0] * 3] * 3  # 看起来像创建3x3矩阵
matrix[0][0] = 1
print(matrix)
# 输出: [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
# 预期: [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

问题根源:列表是可变对象,乘法创建的是引用副本

[[0] * 3] * 3的执行过程是:

  1. 首先创建内部列表[0] * 3,得到[0, 0, 0]
  2. 然后用* 3复制这个列表,但复制的是引用(内存地址)而非创建新列表

这意味着:

  • 矩阵中的三个子列表实际上是同一个列表对象的三个引用
  • 当你修改matrix[0][0]时,其实是修改了那个唯一的子列表对象
  • 由于三个子列表引用的是同一个对象,所以看起来"都变了"

验证:三个子列表是同一个对象

我们可以通过is运算符验证这一点:

matrix = [[0] * 3] * 3
print(matrix[0] is matrix[1])  # True
print(matrix[1] is matrix[2])  # True
# 证明三个子列表是同一个对象

正确的矩阵创建方式

方法1:使用列表推导式(推荐)

matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]] ✅

方法2:显式创建每个子列表

matrix = []
for _ in range(3):
    row = [0] * 3  # 每次循环创建新列表
    matrix.append(row)

这两种方法的关键是:每次都创建新的子列表对象,而不是复制已有列表的引用。

扩展:其他可变对象的类似问题

不只是列表,所有可变对象(字典、集合等)都会有类似问题:

# 字典的错误初始化
dicts = [{}] * 3
dicts[0]['name'] = 'Alice'
print(dicts)  # [{'name': 'Alice'}, {'name': 'Alice'}, {'name': 'Alice'}] ❌

# 正确方式
dicts = [{} for _ in range(3)]
dicts[0]['name'] = 'Alice'
print(dicts)  # [{'name': 'Alice'}, {}, {}] ✅

总结:避免陷阱的原则

当使用*运算符初始化包含可变对象的容器时要特别小心:

  1. *对于不可变对象(如数字、字符串)是安全的:[0] * 3是安全的
  2. *对于可变对象(如列表、字典)会创建引用副本,可能导致意外
  3. 初始化包含可变对象的容器时,推荐使用列表推导式,确保每次创建新对象

记住:可变对象的复制通常是引用复制,而非值复制,这是Python中许多"坑"的根源!

posted @ 2025-10-06 17:16  wangya216  阅读(4)  评论(0)    收藏  举报