06-深拷贝与浅拷贝
难度:⭐⭐ 进阶 预计时间:30分钟 前置知识:列表、字典、引用概念 引入版本:Python 3.0+
本章讲解 Python 的拷贝机制,理解引用、浅拷贝和深拷贝的区别。
为什么需要理解拷贝?
问题场景
你有一个嵌套列表,需要复制一份作为模板:
python
template = [
{"name": "默认用户", "scores": [0, 0, 0]},
{"name": "默认用户", "scores": [0, 0, 0]},
]
user1 = template[0]
user1["name"] = "张三"
user1["scores"][0] = 95
print(user1)
print(template[0])运行结果:
{'name': '张三', 'scores': [95, 0, 0]}
{'name': '张三', 'scores': [95, 0, 0]}问题:
- 你只修改了 user1,但 template[0] 也被修改了!
- 原因:user1 只是一个引用,指向同一个对象
- 这是 Python 开发中最常见的陷阱之一
拷贝概念铺垫
┌─────────────────────────────────────────────────────────────┐
│ 拷贝关键概念 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 什么是引用? │
│ ───────────────────────────────────────────── │
│ • Python 变量不是"盒子",而是"标签" │
│ • a = [1, 2, 3] 意味着:a 标签贴在 [1,2,3] 对象上 │
│ • b = a 意味着:b 标签也贴在同一个对象上 │
│ • 修改 b 也会影响 a(因为指向同一个对象) │
│ │
│ 2. 三种拷贝层次 │
│ ───────────────────────────────────────────── │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 赵引用(Assignment) │ │
│ │ • b = a │ │
│ │ • b 和 a 指向完全相同的对象 │ │
│ │ • 修改任何一个都影响另一个 │ │
│ │ • 这不是真正的"拷贝" │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 浅拷贝(Shallow Copy) │ │
│ │ • 创建新对象 │ │
│ │ • 但内部元素仍是引用 │ │
│ │ • b = a.copy() 或 copy.copy(a) │ │
│ │ • 顶层独立,嵌套元素共享 │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ 深拷贝(Deep Copy) │ │
│ │ • 创建完全独立的新对象 │ │
│ │ • 递归拷贝所有嵌套元素 │ │
│ │ • b = copy.deepcopy(a) │ │
│ │ • 完全独立,修改互不影响 │ │
│ └────────────────────────────────────────────┘ │
│ │
│ 3. 生活类比 │
│ ───────────────────────────────────────────── │
│ 引用:两个人看同一本书 │
│ 浅拷贝:抄作业,但答案部分还是原来的 │
│ 深拷贝:完全抄作业,包括所有细节 │
│ │
│ 4. 何时需要拷贝? │
│ ───────────────────────────────────────────── │
│ • 需要保留原始数据不被修改 │
│ • 多处使用同一模板,各自独立修改 │
│ • 函数参数需要修改但不影响原始值 │
│ │
└─────────────────────────────────────────────────────────────┘L1 理解层:拷贝语法
赵引用 vs 浅拷贝 vs 深拷贝
┌─────────────────────────────────────────────────────────────┐
│ 三种拷贝对比 │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 原始数据 │ │
│ │ a = [[1, 2], [3, 4]] │ │
│ │ │ │
│ │ a ────→ [ 对象A ] │ │
│ │ │ │ │
│ │ ├── [1, 2] (对象B) │ │
│ │ └── [3, 4] (对象C) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ b = a (引用) │ │
│ │ │ │
│ │ a ────→ [ 对象A ] │ │
│ │ │ │ │
│ │ ├── [1, 2] (对象B) │ │
│ │ └── [3, 4] (对象C) │ │
│ │ b ────→ (同一个对象A) │ │
│ │ │ │
│ │ 修改 b[0] 或 b[0][0] 都会影响 a │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ b = a.copy() / copy.copy(a) (浅拷贝) │ │
│ │ │ │
│ │ a ────→ [ 对象A ] │ │
│ │ │ │ │
│ │ ├── [1, 2] (对象B) ←─── 共享 │ │
│ │ └── [3, 4] (对象C) ←─── 共享 │ │
│ │ │ │
│ │ b ────→ [ 对象A' ] ← 新对象 │ │
│ │ │ │ │
│ │ ├── [1, 2] (对象B) ←─── 共享 │ │
│ │ └── [3, 4] (对象C) ←─── 共享 │ │
│ │ │ │
│ │ b[0] = [5, 6] 不影响 a (顶层独立) │ │
│ │ b[0][0] = 5 影响 a (嵌套共享) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ b = copy.deepcopy(a) (深拷贝) │ │
│ │ │ │
│ │ a ────→ [ 对象A ] │ │
│ │ │ │ │
│ │ ├── [1, 2] (对象B) │ │
│ │ └── [3, 4] (对象C) │ │
│ │ │ │
│ │ b ────→ [ 对象A' ] ← 完全独立 │ │
│ │ │ │ │
│ │ ├── [1', 2'] (对象B') ← 完全独立 │ │
│ │ └── [3', 4'] (对象C') ← 完全独立 │ │
│ │ │ │
│ │ 任何修改都不影响对方 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘最简示例:引用
python
a = [1, 2, 3]
b = a
b[0] = 99
print(f"a = {a}")
print(f"b = {b}")
print(f"a is b: {a is b}")运行结果:
a = [99, 2, 3]
b = [99, 2, 3]
a is b: True说明:
b = a只是创建引用,不是拷贝a is b为 True,说明两者指向同一对象- 修改任何一个都会影响另一个
最简示例:浅拷贝
python
import copy
a = [[1, 2], [3, 4]]
b = a.copy()
b[0] = [5, 6]
print(f"a = {a}")
print(f"b = {b}")
print(f"a[0] is b[0]: {a[0] is b[0]}")运行结果:
a = [[1, 2], [3, 4]]
b = [[5, 6], [3, 4]]
a[0] is b[0]: False说明:
a.copy()创建新的顶层列表b[0] = [5, 6]替换顶层元素,不影响 a- 顶层元素独立
python
import copy
a = [[1, 2], [3, 4]]
b = a.copy()
b[0][0] = 99
print(f"a = {a}")
print(f"b = {b}")
print(f"a[0] is b[0]: {a[0] is b[0]}")运行结果:
a = [[99, 2], [3, 4]]
b = [[99, 2], [3, 4]]
a[0] is b[0]: True说明:
- 浅拷贝只复制顶层,嵌套元素仍是引用
b[0][0] = 99修改嵌套元素,影响 aa[0] is b[0]为 True,说明嵌套元素共享
最简示例:深拷贝
python
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0][0] = 99
print(f"a = {a}")
print(f"b = {b}")
print(f"a[0] is b[0]: {a[0] is b[0]}")运行结果:
a = [[1, 2], [3, 4]]
b = [[99, 2], [3, 4]]
a[0] is b[0]: False说明:
copy.deepcopy(a)递归拷贝所有层级- 任何修改都不影响原始数据
a[0] is b[0]为 False,说明完全独立
详细示例:多层嵌套
python
import copy
data = {
"users": [
{"name": "张三", "scores": [85, 90, 78]},
{"name": "李四", "scores": [92, 88, 95]},
],
"config": {"max_score": 100},
}
data_shallow = data.copy()
data_deep = copy.deepcopy(data)
data_shallow["users"][0]["scores"][0] = 0
print(f"原始: {data['users'][0]['scores']}")
print(f"浅拷贝后修改: {data_shallow['users'][0]['scores']}")
data_deep["users"][1]["scores"][1] = 0
print(f"原始(深拷贝修改后): {data['users'][1]['scores']}")
print(f"深拷贝修改: {data_deep['users'][1]['scores']}")运行结果:
原始: [0, 90, 78]
浅拷贝后修改: [0, 90, 78]
原始(深拷贝修改后): [92, 88, 95]
深拷贝修改: [92, 0, 95]说明:
- 浅拷贝:修改嵌套元素影响原始数据
- 深拷贝:完全独立,修改不影响原始
L2 实践层:拷贝应用
拷贝方式总结
| 类型 | 方法 | 顶层独立 | 嵌套独立 | 适用场景 |
|---|---|---|---|---|
| 引用 | b = a | ❌ | ❌ | 不需要独立副本 |
| 浅拷贝 | a.copy() | ✅ | ❌ | 单层列表/字典 |
| 浅拷贝 | copy.copy(a) | ✅ | ❌ | 任意对象 |
| 深拷贝 | copy.deepcopy(a) | ✅ | ✅ | 嵌套数据结构 |
各种数据类型的拷贝方式
python
import copy
a_list = [1, 2, 3]
b_list = a_list.copy()
a_dict = {"a": 1, "b": 2}
b_dict = a_dict.copy()
a_set = {1, 2, 3}
b_set = a_set.copy()
import numpy as np
a_array = np.array([1, 2, 3])
b_array = a_array.copy()
a_object = SomeClass()
b_object = copy.copy(a_object)关键代码说明:
| 数据类型 | 浅拷贝方法 | 说明 |
|---|---|---|
list | .copy() | 创建新列表 |
dict | .copy() | 创建新字典 |
set | .copy() | 创建新集合 |
numpy.ndarray | .copy() | NumPy 数组 |
| 自定义对象 | copy.copy() | 需要 copy 模块 |
推荐做法
| 做法 | 原因 | 示例 |
|---|---|---|
| 嵌套数据用深拷贝 | 避免意外修改原始数据 | copy.deepcopy(template) |
| 单层数据用浅拷贝 | 性能更好,代码更简洁 | data.copy() |
| 检查拷贝是否独立 | 使用 is 运算符验证 | a is b 应为 False |
| 函数参数拷贝 | 函数内修改不影响外部 | process(data.copy()) |
反模式:不要这样做
错误1:误以为切片是深拷贝
python
a = [[1, 2], [3, 4]]
b = a[:]
b[0][0] = 99
print(a)
print(b)运行结果:
[[99, 2], [3, 4]]
[[99, 2], [3, 4]]问题:
a[:]切片是浅拷贝,不是深拷贝- 嵌套元素仍然共享
python
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0][0] = 99
print(a)
print(b)错误2:误解 dict() 构造函数
python
a = {"nested": [1, 2, 3]}
b = dict(a)
b["nested"][0] = 99
print(a)
print(b)运行结果:
{'nested': [99, 2, 3]}
{'nested': [99, 2, 3]}问题:
dict(a)是浅拷贝- 嵌套元素共享
python
import copy
a = {"nested": [1, 2, 3]}
b = copy.deepcopy(a)
b["nested"][0] = 99
print(a)
print(b)错误3:忘记拷贝函数参数
python
def add_score(student: dict, score: int) -> None:
student["scores"].append(score)
students = [{"name": "张三", "scores": [85]}]
add_score(students[0], 90)
print(students)运行结果:
[{'name': '张三', 'scores': [85, 90]}]问题:
- 函数直接修改了原始数据
- 调用者可能不期望原始数据被修改
python
def add_score(student: dict, score: int) -> dict:
import copy
new_student = copy.deepcopy(student)
new_student["scores"].append(score)
return new_student
students = [{"name": "张三", "scores": [85]}]
updated = add_score(students[0], 90)
print(f"原始: {students}")
print(f"更新后: {updated}")适用场景
| 场景 | 拷贝方式 | 原因 |
|---|---|---|
| 单层列表修改 | 浅拷贝 .copy() | 顶层独立足够 |
| 嵌套列表修改 | 深拷贝 deepcopy | 嵌套也需要独立 |
| 函数参数保护 | 浅/深拷贝 | 防止副作用 |
| 模板复制 | 深拷贝 | 模板不应被修改 |
| 缓存数据 | 引用 | 不需要独立副本 |
选择指南:什么时候用哪种拷贝
| 数据结构 | 操作 | 推荐拷贝 | 原因 |
|---|---|---|---|
| 单层 list/dict | 全部修改 | .copy() 浅拷贝 | 顶层独立足够 |
| 嵌套 list/dict | 修改嵌套元素 | deepcopy() | 嵌套需要独立 |
| 不可变元素容器 | 任意修改 | .copy() 浅拷贝 | 元素不可变,无需深拷贝 |
| 模板复用(含嵌套) | 创建独立实例 | deepcopy() | 避免模板污染 |
| 函数接收/返回 | 外部不应被修改 | copy() 或 deepcopy() | 函数副作用隔离 |
| 循环引用结构 | 创建独立副本 | deepcopy() | copy.copy() 会丢失结构 |
反模式:浅拷贝陷阱深度剖析
python
import copy
# ❌ 陷阱 1:修改嵌套 list 的元素(最常见)
template = [[0] * 3 for _ in range(3)]
user1 = copy.copy(template) # 浅拷贝 — 行独立,但格子共享!
user1[0][0] = 99
print(template[0]) # [99, 0, 0] — 原始数据被污染!
# ✅ 正确
user1 = copy.deepcopy(template)
user1[0][0] = 99
print(template[0]) # [0, 0, 0] — 安全
# ❌ 陷阱 2:浅拷贝 dict 后修改嵌套 list
config = {"hosts": ["localhost:8080", "staging:8080"]}
backup = config.copy()
backup["hosts"].append("prod:443") # 修改嵌套 list
print(config["hosts"]) # ['localhost:8080', 'staging:8080', 'prod:443']
# ✅ 正确:深拷贝或手动复制嵌套元素
backup = copy.deepcopy(config)
# ❌ 陷阱 3:误以为 list()、dict()、[:] 是深拷贝
a = [{"key": [1, 2, 3]}]
b = list(a) # 浅拷贝
b[0]["key"][0] = 99
print(a[0]["key"]) # [99, 2, 3] — 被修改了!
# ✅ 正确:只有 copy.deepcopy() 是深拷贝
b = copy.deepcopy(a)
# ✅ 陷阱 4:不可变对象不需要深拷贝
data = {"name": "Alice", "scores": (85, 90, 78)} # tuple 是不可变
backup = data.copy() # 浅拷贝足够 — tuple 成员不可变
backup["name"] = "Bob" # 安全 — 替换顶层值
# backup["scores"] += (95,) # 这不会修改原始 tuple,会创建新的L3 专家层:拷贝原理
Python 对象模型
┌─────────────────────────────────────────────────────────────┐
│ Python 对象存储模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 变量 = 标签 │
│ ───────────────────────────────────────────── │
│ • 变量名只是一个"标签",贴在对象上 │
│ • 多个变量可以贴在同一对象上(引用) │
│ • 对象本身存储在内存中 │
│ │
│ 示例: │
│ a = [1, 2, 3] │
│ b = a │
│ │
│ 内存模型: │
│ ┌─────────────┐ │
│ │ 变量区 │ │
│ │ a (标签) │──┐ │
│ │ b (标签) │──┼──→ ┌─────────────────────┐ │
│ └─────────────┘ │ │ 对象区 │ │
│ │ │ [1, 2, 3] │ │
│ │ │ id: 0x7f1234 │ │
│ └──→ │ type: list │ │
│ │ refcount: 2 │ │
│ └─────────────────────┘ │
│ │
│ 引用计数: │
│ ───────────────────────────────────────────── │
│ • a 和 b 都引用该对象 → refcount = 2 │
│ • a = None → refcount = 1 │
│ • b = None → refcount = 0 → 对象被回收 │
│ │
└─────────────────────────────────────────────────────────────┘id() 和 is 运算符
python
a = [1, 2, 3]
b = a
c = a.copy()
print(f"id(a) = {id(a)}")
print(f"id(b) = {id(b)}")
print(f"id(c) = {id(c)}")
print(f"a is b: {a is b}")
print(f"a is c: {a is c}")运行结果:
id(a) = 140234567890
id(b) = 140234567890 # 与 a 相同
id(c) = 140234567891 # 与 a 不同
a is b: True # 同一对象
a is c: False # 不同对象说明:
id()返回对象的内存地址a is b等价于id(a) == id(b)- 浅拷贝创建新对象,
id不同
copy 模块内部实现
python
import copy
class MyObject:
def __init__(self, value: int) -> None:
self.value = value
def __copy__(self) -> "MyObject":
return MyObject(self.value)
def __deepcopy__(self, memo: dict) -> "MyObject":
return MyObject(self.value * 2)
obj1 = MyObject(10)
obj2 = copy.copy(obj1)
obj3 = copy.deepcopy(obj1)
print(f"obj1.value = {obj1.value}")
print(f"obj2.value = {obj2.value}")
print(f"obj3.value = {obj3.value}")运行结果:
obj1.value = 10
obj2.value = 10
obj3.value = 20说明:
__copy__自定义浅拷贝行为__deepcopy__自定义深拷贝行为memo参数用于处理循环引用
循环引用处理
python
import copy
a = []
a.append(a)
print(f"a[0] is a: {a[0] is a}")
b = copy.deepcopy(a)
print(f"b[0] is b: {b[0] is b}")
print(f"a is b: {a is b}")运行结果:
a[0] is a: True
b[0] is b: True
a is b: False说明:
a.append(a)创建循环引用deepcopy正确处理循环引用(使用 memo dict)- 拷贝后的对象保持循环引用结构
性能考量
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
引用赋值 b = a | O(1) | 只创建标签 |
浅拷贝 a.copy() | O(n) | n = 顶层元素数 |
深拷贝 deepcopy | O(n*m) | 递归拷贝所有层级 |
性能对比:
python
import copy
import time
nested_data = [[i for i in range(100)] for _ in range(1000)]
start = time.time()
shallow = nested_data.copy()
shallow_time = time.time() - start
start = time.time()
deep = copy.deepcopy(nested_data)
deep_time = time.time() - start
print(f"浅拷贝: {shallow_time:.4f}s")
print(f"深拷贝: {deep_time:.4f}s")运行结果:
浅拷贝: 0.0001s
深拷贝: 0.0500s说明:
- 深拷贝性能开销显著更大
- 嵌套层级越深,开销越大
- 只有真正需要时才用深拷贝
设计动机
| 设计选择 | 原因 |
|---|---|
| 变量是引用 | 简化对象传递,避免不必要的拷贝 |
| 浅拷贝作为默认 | 性能优先,大多数场景足够 |
| 深拷贝需要显式调用 | 避免性能浪费,明确意图 |
提供 __copy__/__deepcopy__ | 允许自定义拷贝行为 |
知识关联
知识关联图:
┌─────────────────┐ ┌─────────────────┐
│ 引用概念 │────→│ 浅拷贝/深拷贝 │
│ 变量=标签 │ │ copy模块 │
└─────────────────┘ └─────────────────┘
│ │
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ id()函数 │ │ 循环引用 │
│ is运算符 │ │ memo dict │
└─────────────────┘ └─────────────────┘
│ │
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ 引用计数 │ │ 自定义拷贝 │
│ 垃圾回收 │ │ __copy__ │
└─────────────────┘ └─────────────────┘引用计数与拷贝
python
import sys
# 查看对象的引用计数
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2(a 变量 + getrefcount 参数)
b = a # 增加引用
print(sys.getrefcount(a)) # 3
del b
print(sys.getrefcount(a)) # 2 — 引用计数减少
# 浅拷贝不增加原始嵌套对象的引用计数
import copy
a = [[1, 2], [3, 4]]
print(sys.getrefcount(a[0])) # 引用计数 = 2(a[0] + 参数)
b = copy.copy(a)
print(sys.getrefcount(a[0])) # 引用计数 = 3(b[0] 指向同一对象!)
# 浅拷贝:嵌套对象被共享,引用计数 +1
b = copy.deepcopy(a)
print(sys.getrefcount(a[0])) # 引用计数不变 — 深拷贝创建了新对象copy 模块内部实现原理
python
# copy.copy() 的工作原理(简化):
# 1. 检查对象的 __copy__ 方法 → 有则调用
# 2. 检查对象的 __reduce_ex__ / __reduce__ → 有则使用
# 3. 检查对象类型的 __copy__ 方法 → 找到则调用
# 4. 对内置类型的优化路径:
# - list: 调用 list.copy()
# - dict: 调用 dict.copy()
# - set: 调用 set.copy()
# copy.deepcopy() 的工作原理(简化):
# 1. 维护 memo dict(id → 新对象映射),处理循环引用
# 2. 检查 memo → 如果已拷贝过,直接返回
# 3. 检查 __deepcopy__ 方法 → 有则调用
# 4. 递归拷贝所有属性/元素
# 5. 对不可变对象(int, str, tuple 等):直接返回引用(优化)
import copy
# 不可变对象的深拷贝优化 — 直接返回自身
a = (1, 2, 3)
b = copy.deepcopy(a)
print(a is b) # True — 不可变对象无需真正拷贝
a = "hello"
b = copy.deepcopy(a)
print(a is b) # True — 字符串也是不可变
# 含不可变嵌套的结构 — 部分优化
data = [1, 2, (3, 4)] # 顶层 list 是可变,但嵌套 tuple 不可变
b = copy.deepcopy(data)
print(data[2] is b[2]) # True — 不可变的 tuple 被共享!
# deepcopy 不会浪费资源拷贝不可变对象自定义 copy 行为
python
import copy
class TreeNode:
def __init__(self, value, children=None):
self.value = value
self.children = children or []
def __deepcopy__(self, memo):
"""自定义深拷贝 — 跳过不需要的部分"""
if id(self) in memo:
return memo[id(self)]
new_node = TreeNode(self.value) # value 不可变,直接共享
memo[id(self)] = new_node
new_node.children = [copy.deepcopy(c, memo) for c in self.children]
return new_node
# 验证
root = TreeNode(1, [TreeNode(2), TreeNode(3)])
clone = copy.deepcopy(root)
print(clone.value) # 1
print(len(clone.children)) # 2
print(root is clone) # False — 完全独立
print(root.children[0] is clone.children[0]) # False — 递归拷贝自检清单
回答以下问题,检查你是否掌握了核心概念:
b = a是拷贝吗?为什么?- 浅拷贝和深拷贝的区别是什么?
- 如何判断两个对象是否指向同一内存?
- 什么时候用深拷贝,什么时候用浅拷贝?
- 切片
a[:]是深拷贝还是浅拷贝?
本章术语表
| 术语 | 定义 | 本章位置 |
|---|---|---|
| 引用 | 变量指向对象的标签 | 概念铺垫 |
| 浅拷贝 | 创建新对象,嵌套元素共享 | L1理解层 |
| 深拷贝 | 递归拷贝所有层级,完全独立 | L1理解层 |
| id() | 返回对象的内存地址 | L3专家层 |
| is | 判断两个对象是否同一 | L3专家层 |
| copy | 自定义浅拷贝行为 | L3专家层 |
| deepcopy | 自定义深拷贝行为 | L3专家层 |
扩展阅读
- Python 官方文档:copy 模块
- 《流畅的Python》第8章:对象引用、可变性和垃圾回收
- Python 内存管理:引用计数与垃圾回收