📄 一句话概括
浅复制只复制最外层——shallow.items 和 origin.items 是同一个列表,改一个两个都变。
📝 本节和前面几节的关系
5.9 说变量存地址,这一节说想真复制的时候会踩什么坑。
一、三种操作
y = x:只复制地址,外层内层全共用。
copy.copy(x):新建一个外壳,内层还是共用——这叫浅复制。
copy.deepcopy(x):外壳内层全新建——这叫深复制。
三者的区别只在【复制的深度】,不在对象大小。
二、实测
shallow.items.append(3) 之后 origin.items 也变成 [1, 2, 3]。
deep.items.append(4) 之后 origin.items 还是 [1, 2, 3]。
同一段代码,只因为复制方式不同,结果一个是【跟着变】一个是【不变】。
三、最常见的两个坑
默认参数:def f(items=[]) 里的那个列表只创建一次,所有调用共享。
缓存:把对象塞进缓存时不复制,之后改对象等于偷偷改了缓存。
这两个坑的共同点:你以为是新的,其实是同一个。
四、怎么判断
只问一句:内层对象要不要独立?
要独立——deepcopy;只要外壳独立——copy;都无所谓——直接赋值。
最贵的不是 deepcopy 的开销,是复制错了之后查三天。
五、动手 Demo:复制存储实例的变量时要多加注意
一个对象装着一个列表,分别浅复制和深复制一份。 改内层,看原件跟不跟着变。
import copy
origin = Bag([1, 2])
shallow = copy.copy(origin)
deep = copy.deepcopy(origin)
shallow.items.append(3)
deep.items.append(4)
print(origin.items, shallow.items, deep.items)
实跑输出:
--------------------------------------------------------------------
复制存储实例的变量时要多加注意
--------------------------------------------------------------------
浅复制和深复制,**差在【内层对象复不复制】上。**实测:
origin.items [1, 2, 3]
shallow.items [1, 2, 3]
deep.items [1, 2, 4]
改 shallow 的内层,origin 跟着变吗 变
改 deep 的内层,origin 跟着变吗 不变
操作 外层 内层 后果
---------------------------------------------------
y = x 共用 共用 改哪都变
copy.copy 新建 共用 改内层两个都变
copy.deepcopy 新建 新建 互不影响
**浅复制只复制了最外面那一层**——`shallow.items` 和 `origin.items` 是同一个列表。
**这个坑最常在【缓存】和【参数默认值】上踩**:默认参数只算一次,所有调用共享。
**记一条:要独立副本,先用 deepcopy;只想共享,就别复制。**
实测:浅复制跟着变(origin 也成 [1, 2, 3]),深复制不跟着变(还是 [1, 2, 3])。
浅复制只复制最外面一层——shallow.items 和 origin.items 是同一个列表。
30 秒实验
- 找一个带列表默认参数的函数,多调两次,看列表是不是越积越多。
- 把一次
copy.copy换成deepcopy,看行为变不变。
常见误区
- 以为 copy.copy 就是全部复制。它只复制最外层。
- 以为复制总比不复制安全。复制错了比不复制更难查。
自测三问(点开对答案)
1. 三种操作各复制了什么? —— `y = x` 只复制地址;`copy.copy` 新建外壳、内层共用;`copy.deepcopy` 外壳内层全新建。 2. 实测浅复制和深复制差在哪? —— 浅复制后改内层原件跟着变([1,2,3]);深复制不跟着变,原件还是 [1,2,3]。 3. 最常见的两个坑是什么? —— 列表默认参数(只创建一次、所有调用共享)和缓存(塞进去不复制,后来被偷偷改)。- ☐ 下一节(5.11 多态让不同的类看起来一样)
相关笔记
- 在变量中存储实例的指针 — 为什么赋值不算复制
- 多态让不同的类看起来一样 — 指针的另一面用处
- 🔷 C++基础 · 索引 — 浅拷贝与深拷贝
- 🧩 面向对象是怎样工作的 · 索引