📄 一句话概括
y = x 只复制地址——id 相同、引用计数 3、改 y.w 时 x.w 一起变成 99。
📝 本节和前面几节的关系
5.8 说实例在堆上,这一节说变量里到底存了什么。
一、存的是地址
变量里存的不是物体本身,是它的地址(引用、指针)。
所以 x = Rect(6, 4) 干了两件事:在堆上开一块,把这块的地址放进 x。
这就是 5.1 那四步里【找变量】那一步的全部内容。
二、实测
y = x 之后 id(x) == id(y) 是 True——两个名字,一个物体。
x 的引用计数是 3(x、y、getrefcount 自己那一份)。
y.w = 99 之后 x.w 也是 99——因为改的是同一块。
名字多了不算复制,只是多了一个指向它的箭头。
三、赋值不是复制
赋值只搬地址,代价是一次指针拷贝,跟对象多大无关。 所以传参也是传地址:把对象传进函数,函数里改字段,外面看得见。 这是效率的来源,也是 bug 的来源——你以为传了个副本,其实传了把钥匙。
四、这引出 5.10
想真复制怎么办?得显式动手:copy.copy 或 copy.deepcopy。
而这两个还不一样:浅的只复制外层,深的连内层一起。
所以 5.10 讲的是【复制】这件事本身有多容易踩坑。
五、动手 Demo:在变量中存储实例的指针
让两个变量指向同一个实例,验证它们其实是同一个物体。 再改一边,看另一边会不会跟着变。
x = Rect(6, 4)
y = x
print(id(x) == id(y)) # True:同一个物体
print(sys.getrefcount(x)) # 3:x、y、传参
y.w = 99
print(x.w) # 99:一起变了
实跑输出:
--------------------------------------------------------------------
在变量中存储实例的指针
--------------------------------------------------------------------
变量里存的**不是对象本身,是它的地址**。所以赋值不复制对象:
x = Rect(6, 4)
y = x
id(x) 与 id(y) 相同吗 True
x 的引用计数 3
y.w = 99 之后 x.w 99 ← 一起变了
写法 复制了什么 改一边另一边
---------------------------------------------------------------
y = x 只复制了地址 跟着变
copy.copy(x) 对象本体(内层还是共用) 内层会变
copy.deepcopy(x) 对象连内层一起 互不影响
**`y = x` 之后,两个名字指向同一块堆内存**——改哪个都是改同一处。
**想真正多一份,必须显式复制;而复制还有【浅】和【深】两种。**
**这就是 5.10 要专门讲的那个坑。**
实测:id 相同、引用计数 3、改 y.w 之后 x.w 也变成 99。 赋值只搬地址,不复制物体——这既是效率的来源,也是【以为传了副本其实传了钥匙】的来源。
30 秒实验
- 把对象传进一个函数,在函数里改字段,看外面变不变。
- 用
id()对比【重新赋值】和【复制】两种写法。
常见误区
- 以为 y = x 复制了对象。只复制地址。
- 以为传参是复制。传的也是地址。
自测三问(点开对答案)
1. 变量里存的是什么? —— 存的是对象在堆上的地址(引用/指针),不是对象本身。 2. y = x 之后实测是什么? —— id 相同、引用计数 3、改 y.w 后 x.w 一起变成 99——两个名字一个物体。 3. 为什么这既是效率也是 bug 来源? —— 赋值只搬地址,代价与对象大小无关,所以快;但你以为传了副本,其实传了把钥匙,改一处影响一片。- ☐ 下一节(5.10 复制存储实例的变量时要多加注意)
相关笔记
- 复制存储实例的变量时要多加注意 — 想真复制会踩什么
- 类指类型,实例指具体的物 — 身份是什么
- 🔵 计算机基础 · 索引 — 指针与引用
- 🧩 面向对象是怎样工作的 · 索引