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
的执行过程是:
- 首先创建内部列表
[0] * 3
,得到[0, 0, 0]
- 然后用
* 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'}, {}, {}] ✅
总结:避免陷阱的原则
当使用*
运算符初始化包含可变对象的容器时要特别小心:
*
对于不可变对象(如数字、字符串)是安全的:[0] * 3
是安全的*
对于可变对象(如列表、字典)会创建引用副本,可能导致意外- 初始化包含可变对象的容器时,推荐使用列表推导式,确保每次创建新对象
记住:可变对象的复制通常是引用复制,而非值复制,这是Python中许多"坑"的根源!