python中二维数组初始化陷阱
python变量是一个储存对象地址的变量(可以看作为一个动态标签,可以表示任意数据类型)
matrix1 = [[0] * 3 for _ in range(3)] matrix2 = [[0] * 3] * 3matrix2 = [[0]*3]*3的运行步骤
1. 第一步:[0] * 3
当你写[0] * 3时,Python 确实把0的内存地址复制了 3 份。
在底层,这个列表其实长这样:
row ──> [ 索引 0 , 索引 1 , 索引 2 ] │ │ │ └─────────┼─────────┘ ▼ [ 0 ] (不可变对象)你看 navigation 里的三个索引,其实都指向同一个0。这和第二步的连线方式一模一样!
为什么说它是安全的?
因为0是不可变对象。你没有任何办法去“原地修改”这个0。
- 如果你执行
row[0] = 5,你并不是把0变成了5。 - 你做的是:在内存里找个新地方放
5,然后把索引 0的连线掐断,重新连到5上。
row ──> [ 索引 0 , 索引 1 , 索引 2 ] │ │ │ ▼ └─────────┤ [ 5 ] ▼ [ 0 ]此时,索引 1和索引 2依然雷打不动地指向0。所以结果是[5, 0, 0],完美符合预期。
2. 第二步:[[0, 0, 0]] * 3
现在外层再次使用* 3,Python 同样复制了 3 次内部列表的地址。
matrix ──> [ 索引 0 , 索引 1 , 索引 2 ] │ │ │ └─────────┼─────────┘ ▼ [0, 0, 0] (可变对象)为什么这时候崩盘了?
因为内部列表是可变对象,它支持原地修改(比如通过append()或[0]=1)。
- 当你执行
matrix[0][0] = 1时,你没有去掐断matrix对子列表的连线。 - 你是顺着
索引 0的连线,直接走进了那个子列表的内部,把里面的第一个元素改成了1。
因为索引 1和索引 2连接的也是这同一个子列表的大门,当你从大门走进去看时,里面已经被改掉了。
正确写法
如果你想创建独立的行,必须让 Python每次都运行一次外壳,真正去新建一个列表。最推荐、最 Pythonic 的写法是使用列表推导式(List Comprehension):
Python
matrix = [[0] * 3 for _ in range(3)]为什么这样就行了?因为for _ in range(3)是一个循环。在这个循环里,内层的[0] * 3被独立执行了 3 次。每次执行都会在内存中开辟一块全新的、干净的空间来存放新列表。
此时的内存结构就变成了:
Plaintext
matrix ──> [ 索引 0 , 索引 1 , 索引 2 ] │ │ │ ▼ ▼ ▼ [0,0,0] [0,0,0] [0,0,0] (三个独立的列表,互不干扰)💡 核心总结
用一句话把这个行为说透:
Python 的
*操作符一视同仁,永远只复制“引用(地址)”。
- 复制不可变对象的地址是安全的,因为没人能原地改它,要改只能换新地址(解绑)。
- 复制可变对象的地址是危险的,因为任何人都可以顺着地址进去把里面的内容改掉,从而连累所有指向它的人。
| 特性 | 不可变对象 (Immutable) | 可变对象 (Mutable) |
|---|---|---|
| 包含类型 | int,float,str,tuple,bool | list,dict,set |
| 能否原地修改 | ❌ 不能 | 能 |
| 修改时的底层操作 | 放弃原对象,创建全新对象(开辟新内存) | 直接修改原对象内部的值(内存地址不变) |
| 常见误区 | 以为重新赋值就是修改了原对象 | 容易像上一题一样,多个变量共享同一个内存导致“牵一发而动全身” |
关于验证两个变量(比如两个列表)是否指向同一个内存地址
方法一:使用is运算符(最推荐、最直观)
is是 Python 的身份运算符。它专门用来检查两个变量的内存地址是否完全相同。
- 如果返回
True,说明它们指向同一个内存地址(是同一个对象)。 - 如果返回
False,说明它们在内存中是相互独立的(哪怕它们的内容长得一模一样)。
💻 代码验证:
Python
# 情况 1:使用 * 3 复制导致的“同地址”陷阱 matrix = [[0, 0, 0]] * 3 # 检查第一行和第二行是不是同一个列表 print(matrix[0] is matrix[1]) # 输出: True (完蛋,指向同一个地址) # 情况 2:使用列表推导式创建的“独立”列表 correct_matrix = [[0, 0, 0] for _ in range(3)] # 检查第一行和第二行是不是同一个列表 print(correct_matrix[0] is correct_matrix[1]) # 输出: False (安全,互不干扰)方法二:使用id()函数(看清本质)
id()是 Python 的内置函数,它会返回一个对象的唯一内存地址(在 CPython 中,它通常表现为一个很长的整数,代表该对象在内存中的实际物理地址)。
如果两个变量的id()结果完全一样,就证明它们在内存中是同一个东西。
💻 代码验证:
Python
matrix = [[0, 0, 0]] * 3 # 打印三行的 id(内存地址) print(id(matrix[0])) # 输出类似: 140228345739520 print(id(matrix[1])) # 输出类似: 140228345739520 print(id(matrix[2])) # 输出类似: 140228345739520 # 三个地址一模一样,说明它们在内存里其实是同一个箱子!⚠️ 延伸:==和is的巨大区别
这也是面试中几乎必考的考点。请一定要分清:
==(重在内容):检查两个对象的值(内容)是否相等。is(重在身份):检查两个对象是不是同一个内存地址。
Python
list_a = [1, 2, 3] list_b = [1, 2, 3] print(list_a == list_b) # 输出: True (因为里面的内容都是 1, 2, 3) print(list_a is list_b) # 输出: False (因为它们是分别创建的,住在不同的内存箱子里)