一句话结论
Python 变量存的是指针,不是值。容器(list/dict)里装的也是指针。顺着指针改东西,所有持有同一指针的人都看得到。
1. 变量是标签,不是盒子
很多语言(C/Java 基本类型)里,变量像一个盒子,值装在盒子里。Python 不是这样------变量是贴在对象上的标签:
python
a = [1, 2, 3]
b = a
css
a ──┐
▼
[1, 2, 3] ← 内存中只有一个 list 对象
▲
b ──┘
a 和 b 是两张标签,贴在同一个对象上。
2. 赋值 vs 修改:本质区别
赋值(Rebinding)= 撕标签贴到别处
python
a = [1, 2, 3]
b = a
b = [4, 5, 6] # b 标签撕下来,贴到新对象上
print(a) # [1, 2, 3] --- a 纹丝不动
css
a ───► [1, 2, 3] ← 原对象没人动
b ───► [4, 5, 6] ← 新对象
修改(Mutation)= 顺着标签改对象
python
a = [1, 2, 3]
b = a
b.append(4) # 顺着 b 标签找到对象,改了它
print(a) # [1, 2, 3, 4] --- a 也变了!
css
a ──┐
▼
[1, 2, 3, 4] ← 同一个对象被改了
▲
b ──┘
3. 容器里装的也是指针
python
person = {"name": "张三", "age": 25}
team = [person]
person["age"] = 26
print(team[0]["age"]) # 26 --- team 里的也变了!
内存模型:
css
person ──┐
▼
team[0] ─► {"name": "张三", "age": 26} ← 只有一个 dict
team[0] 和 person 是指向同一个 dict 的两个指针。通过任何一个指针修改 dict,另一个都能看到。
4. 函数传参:同样的规则
python
def add_tag(user):
user["vip"] = True # 修改了传入的 dict
info = {"name": "李四"}
add_tag(info)
print(info) # {"name": "李四", "vip": True} --- 被改了!
函数参数 user 和外部的 info 指向同一个 dict。在函数内修改 dict 属性,外面立刻可见。
但如果函数内部做的是赋值:
python
def reset_user(user):
user = {"name": "新人"} # 只是局部变量换了指向
info = {"name": "李四"}
reset_user(info)
print(info) # {"name": "李四"} --- 没变!
5. 完整分类:哪些操作外部可见?
| 操作类型 | 示例 | 外部可见? | 原因 |
|---|---|---|---|
| 修改 dict 属性 | d["key"] = val |
可见 | 顺着指针改对象 |
| 修改 list 元素 | lst[0] = val |
可见 | 顺着指针改对象 |
| list 追加 | lst.append(x) |
可见 | 顺着指针改对象 |
| list 删除 | lst.pop() / del lst[0] |
可见 | 顺着指针改对象 |
| dict 删除 key | del d["key"] |
可见 | 顺着指针改对象 |
| 调用修改方法 | lst.sort() / lst.reverse() |
可见 | 顺着指针改对象 |
| 变量赋值 | lst = [] |
不可见 | 只是局部标签换了方向 |
规律 :只有 变量 = xxx 这种裸赋值 会断开引用。其他一切操作([]、.、方法调用)都是顺着引用链改对象本身。
6. 嵌套结构:引用链可以很深
python
company = {
"departments": [
{"name": "技术部", "members": ["Alice", "Bob"]},
{"name": "产品部", "members": ["Charlie"]},
]
}
# 取出深层引用
tech = company["departments"][0]
tech["members"].append("Dave")
print(company["departments"][0]["members"])
# ["Alice", "Bob", "Dave"] --- 跟着改了
无论嵌套多深,只要顺着引用链走,修改的就是同一个底层对象。
7. 如何"断开"引用?------复制
如果你不想影响原对象,需要显式复制:
浅拷贝(一层)
python
import copy
a = [{"x": 1}, {"x": 2}]
b = a.copy() # 或 b = list(a) 或 b = a[:]
b.append({"x": 3}) # b 多了一个,a 没有 ✅
b[0]["x"] = 99 # 但 a[0]["x"] 也变了 ❌(里面的 dict 还是共享的)
浅拷贝只复制一层------list 是新的,但里面的 dict 还是共享同一个。
深拷贝(递归所有层)
python
import copy
a = [{"x": 1}, {"x": 2}]
b = copy.deepcopy(a)
b[0]["x"] = 99
print(a[0]["x"]) # 1 --- 完全隔离 ✅
8. 可变 vs 不可变
| 类型 | 可变? | 能被"穿透修改"? |
|---|---|---|
| list | 可变 | 能 |
| dict | 可变 | 能 |
| set | 可变 | 能 |
| 自定义对象 | 可变 | 能 |
| int / float | 不可变 | 不能 |
| str | 不可变 | 不能 |
| tuple | 不可变 | 不能(但里面的可变元素能) |
tuple 的特殊情况:
python
t = ([1, 2], [3, 4])
t[0].append(5) # ✅ 合法!tuple 不能换元素,但元素自己可以变
print(t) # ([1, 2, 5], [3, 4])
9. 判断"是不是同一个对象"
python
a = [1, 2, 3]
b = a
c = a.copy()
print(a is b) # True --- 同一个对象
print(a is c) # False --- 不同对象(内容相同但内存地址不同)
print(a == c) # True --- 值相等
print(id(a) == id(b)) # True
print(id(a) == id(c)) # False
is/id()→ 判断是不是同一个对象(同一块内存)==→ 判断值是否相等(内容一样就行)
10. 一图总结
go
┌─────────────────────────────────────────────────────────────┐
│ Python 引用模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 变量 ──► 对象 │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 赋值 x = y │ │ 修改 x[i] = y │ │
│ │ • 标签换方向 │ │ • 顺着标签改对象 │ │
│ │ • 原对象不受影响 │ │ • 所有引用者可见 │ │
│ │ • 只影响当前变量 │ │ • 不产生新对象 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ 想隔离?→ copy.copy()(浅)或 copy.deepcopy()(深) │
│ │
└─────────────────────────────────────────────────────────────┘
记忆口诀
赋值换方向,修改穿到底。
=只是把标签撕下来贴到别处,不碰原对象;
[]、.是顺着标签找到对象动手术,所有人都受影响。