Skip to content

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 修改嵌套元素,影响 a
  • a[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 = aO(1)只创建标签
浅拷贝 a.copy()O(n)n = 顶层元素数
深拷贝 deepcopyO(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 — 递归拷贝

自检清单

回答以下问题,检查你是否掌握了核心概念:

  1. b = a 是拷贝吗?为什么?
  2. 浅拷贝和深拷贝的区别是什么?
  3. 如何判断两个对象是否指向同一内存?
  4. 什么时候用深拷贝,什么时候用浅拷贝?
  5. 切片 a[:] 是深拷贝还是浅拷贝?

本章术语表

术语定义本章位置
引用变量指向对象的标签概念铺垫
浅拷贝创建新对象,嵌套元素共享L1理解层
深拷贝递归拷贝所有层级,完全独立L1理解层
id()返回对象的内存地址L3专家层
is判断两个对象是否同一L3专家层
copy自定义浅拷贝行为L3专家层
deepcopy自定义深拷贝行为L3专家层

扩展阅读

  • Python 官方文档:copy 模块
  • 《流畅的Python》第8章:对象引用、可变性和垃圾回收
  • Python 内存管理:引用计数与垃圾回收