Python 可变对象与引用穿透:为什么改了"里面的东西"外面也变了?

一句话结论

Python 变量存的是指针,不是值。容器(list/dict)里装的也是指针。顺着指针改东西,所有持有同一指针的人都看得到。


1. 变量是标签,不是盒子

很多语言(C/Java 基本类型)里,变量像一个盒子,值装在盒子里。Python 不是这样------变量是贴在对象上的标签

python 复制代码
a = [1, 2, 3]
b = a
css 复制代码
  a ──┐
      ▼
      [1, 2, 3]    ← 内存中只有一个 list 对象
      ▲
  b ──┘

ab 是两张标签,贴在同一个对象上。


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()(深)          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

记忆口诀

赋值换方向,修改穿到底。

= 只是把标签撕下来贴到别处,不碰原对象;
[]. 是顺着标签找到对象动手术,所有人都受影响。

相关推荐
woon4 小时前
从“涂掉红色”到“删除 PDF 对象”:一次 PDF 去印章脚本改造实践
python
老纪5 小时前
c++怎么利用std--variant处理多种二进制子协议包的自动分支解析【进阶】
jvm·数据库·python
茗创科技5 小时前
Nat Hum Behav | 特征选择会导致基于脑影像的机器学习生物标志物产生迥异的神经生物学解释
python·深度学习·机器学习·matlab·脑网络
IT策士5 小时前
Django 从 0 到 1 打造完整电商平台:Django 模型进阶与数据迁移
python·django·sqlite
OsDepK5 小时前
AudioSplit音频多轨免费分离工具即将发布
ide·git·python·音视频·集成学习
Metaphor6925 小时前
使用 Python 将 Excel 转换为 PDF
python·pdf·excel
彦为君6 小时前
长时间运行的 Agent:如何设计可靠的执行框架
python·ai·ai编程
qqqweiweiqq6 小时前
Jetson Orin nx 无法train pi0
人工智能·python·深度学习
AAA大运重卡何师傅(专跑国道)6 小时前
scrapling框架源码5/19
python