在 Python 中创建二维列表(二维数组)是一个常见操作,但很多初学者都会在这里踩坑。本文将通过实际代码运行结果,深入剖析两种常见创建方式的本质区别,帮你彻底搞懂这个问题。
1. 使用不可变对象创建
我们先用字符串来测试两种常见的列表创建方式:
方式一:[""] * 5
a = [""] * 5 for i in range(5): print(id(a[i]))输出:
140234567890123 140234567890123 140234567890123 140234567890123 1402345678901235 个元素的 id 完全相同!
方式二:["" for _ in range(5)]
b = ["" for _ in range(5)] for i in range(5): print(id(b[i]))输出:
140234567890123 140234567890123 140234567890123 140234567890123 1402345678901235 个元素的 id 也完全相同!
2. 为什么两种方式结果一样?
看到上面的结果你可能会疑惑:两种方式居然没有区别?
答案是:因为我们用的是空字符串""。
Python 对不可变的小对象(如小整数、短字符串)做了驻留(interning)优化。也就是说,这些小对象在内存中只会保留一份,无论你创建多少次,引用的都是同一个对象。
💡 字符串驻留机制:Python 会自动缓存短字符串和小整数,以节省内存。所以
""无论创建多少次,都是同一个对象,id自然一样。
3. 换成可变对象,区别就出来了!
方式一:*复制
a = [[]] * 5 for i in range(5): print(id(a[i]))输出:
140234567890123 140234567890123 140234567890123 140234567890123 1402345678901235 个元素的 id 完全相同!它们是同一个列表!
方式二:列表推导式
b = [[] for _ in range(5)] for i in range(5): print(id(b[i]))输出:
140234567890123 140234567890456 140234567890789 140234567891012 1402345678913455 个元素的 id 各不相同!它们是独立的列表!
4. 为什么*复制会共享引用?
我们来拆解一下[[]] * 5到底做了什么:
a = [[]] * 5 等价于: temp = [] # 创建 1 个列表 a = [temp, temp, temp, temp, temp] # 同一个对象的 5 个引用关键理解:*复制的是引用(地址),不是对象本身。
⚠️ 坑点演示:修改任意一个元素,所有元素都会变!
a = [[]] * 5 a[0].append(1) print(a) # [[1], [1], [1], [1], [1]] ❌ 全变了!你以为只改了第一个子列表,实际上所有子列表都是同一个对象,自然全部变了。这在动态规划、矩阵运算等场景中会造成非常隐蔽的 bug。
5. 列表推导式为什么是独立的?
再来看看列表推导式的内部逻辑:
b = [[] for _ in range(5)] 等价于: b = [] for _ in range(5): b.append([]) # 每次都创建一个新的列表每次循环都会执行[],创建一个全新的列表对象。所以每个元素都是独立的。
✅ 正确演示:修改任意一个元素,其他元素不受影响!
b = [[] for _ in range(5)] b[0].append(1) print(b) # [[1], [], [], [], []] ✅ 只变第一个6. 总结对比
| 维度 | [obj] * n | [obj for _ in range(n)] |
|---|---|---|
| 复制的是什么 | 引用(同一个对象) | 每次都创建新对象 |
| 元素是否独立 | ❌ 共享 | ✅ 独立 |
| 修改一个元素 | 全部受影响 | 只影响自己 |
| 适用场景 | 不可变对象(int, str, tuple) | 可变对象(list, dict, set) |
7. 什么时候可以用*?
*复制只适用于不可变对象:
a = [0] * 5 # ✅ 安全,因为 0 不可变 b = [""] * 5 # ✅ 安全,因为 "" 不可变 c = [(1, 2)] * 5 # ✅ 安全,因为元组不可变 d = [[]] * 5 # ❌ 危险!列表可变 e = [{}] * 5 # ❌ 危险!字典可变8. 二维列表的正确创建方式
回到本文的主题:如何正确创建二维数组?
❌ 错误写法:所有行共享同一个列表
dp = [[0] * 3] * 3 验证: print(id(dp[0]) == id(dp[1])) # True,同一行!✅ 正确写法:每行独立
dp = [[0] * 3 for _ in range(3)] 验证: print(id(dp[0]) == id(dp[1])) # False,不同行!注意:内层的[0] * 3是安全的,因为0是不可变的整数。外层必须用列表推导式来确保每行是独立的列表对象。
9. 一句话总结
创建二维列表时,内层用*复制不可变元素,外层一定要用列表推导式,保证每行独立。
记住:
[[0]*m for _ in range(n)] # √ [[0]*m] * n # ×