深拷贝与浅拷贝
对象、变量与引用
理解深拷贝和浅拷贝前,需要先区分变量和对象。Python 变量保存的是对对象的引用,赋值通常只是让另一个变量引用同一个对象,并不会自动复制对象。
original = [1, 2, 3]
alias = original
alias.append(4)
print(original) # [1, 2, 3, 4]
print(alias is original) # True
alias = original 只是增加了一个引用。两个变量指向同一列表,因此通过任意一个变量修改列表,另一个变量都能观察到变化。
浅拷贝
浅拷贝(Shallow Copy)会创建一个新的外层容器,但不会递归复制容器内部的对象。新旧容器中的嵌套元素仍然指向相同对象。
创建浅拷贝
列表常见的浅拷贝方式包括:
import copy
original = [1, 2, 3]
copy_by_method = original.copy()
copy_by_slice = original[:]
copy_by_constructor = list(original)
copy_by_module = copy.copy(original)
字典和集合也提供 copy():
settings = {"theme": "dark", "features": ["search"]}
settings_copy = settings.copy()
tags = {"python", "copy"}
tags_copy = tags.copy()
浅拷贝的嵌套对象
original = {
"name": "Alice",
"skills": ["Python", "SQL"],
}
shallow = original.copy()
shallow["name"] = "Bob"
shallow["skills"].append("Git")
print(original["name"]) # Alice
print(shallow["name"]) # Bob
print(original["skills"]) # ['Python', 'SQL', 'Git']
修改 shallow["name"] 只替换了新字典中的引用,不影响原字典;但两个字典的 skills 都引用同一个列表,所以对列表执行 append() 会同时反映在两边。
可以通过 is 验证对象身份:
print(shallow is original) # False
print(shallow["skills"] is original["skills"]) # True
深拷贝
深拷贝(Deep Copy)不仅创建新的外层对象,还会递归复制其中包含的可变对象,使副本尽量形成独立的对象结构。
Python 使用 copy.deepcopy() 创建深拷贝:
import copy
original = {
"name": "Alice",
"skills": ["Python", "SQL"],
}
deep = copy.deepcopy(original)
deep["skills"].append("Git")
print(original["skills"]) # ['Python', 'SQL']
print(deep["skills"]) # ['Python', 'SQL', 'Git']
print(deep["skills"] is original["skills"]) # False
深拷贝会维护一个已经复制过的对象记录,因此能够保留原对象内部的共享关系,并避免在常见循环引用中无限递归。
import copy
original = []
original.append(original)
deep = copy.deepcopy(original)
print(deep is deep[0]) # True
核心区别
假设对象结构如下:
外层列表
├── 整数 1
└── 内层列表 [2, 3]
不同操作的结果可以概括为:
直接赋值:外层和内层都共享
浅拷贝:外层独立,内层共享
深拷贝:外层独立,内层也独立
| 对比项 | 直接赋值 | 浅拷贝 | 深拷贝 |
|---|---|---|---|
| 是否创建新外层对象 | 否 | 是 | 是 |
| 是否递归复制嵌套对象 | 否 | 否 | 是 |
| 嵌套可变对象是否共享 | 是 | 是 | 通常否 |
| 常用写法 | alias = original | copy.copy()、.copy()、切片 | copy.deepcopy() |
| 时间与内存成本 | 最低 | 较低 | 较高 |
| 典型用途 | 有意共享同一对象 | 只需独立修改外层结构 | 需要独立修改整个对象图 |
不可变对象的影响
整数、浮点数、字符串、布尔值等不可变对象不能被原地修改,因此复制操作可能安全地复用它们:
import copy
name = "Python"
deep_name = copy.deepcopy(name)
print(deep_name is name) # 在当前实现中通常为 True
这并不违反深拷贝的语义,因为不可变对象无法被一方修改后影响另一方。
元组本身不可变,但元组可能包含可变对象:
import copy
original = ([1, 2], "fixed")
deep = copy.deepcopy(original)
deep[0].append(3)
print(original[0]) # [1, 2]
判断是否需要深拷贝时,应关注整个对象结构中是否包含可能被修改的嵌套对象,而不能只看最外层类型。
自定义对象
浅拷贝和深拷贝同样适用于类实例:
import copy
class User:
def __init__(self, name, skills):
self.name = name
self.skills = skills
original = User("Alice", ["Python"])
shallow = copy.copy(original)
deep = copy.deepcopy(original)
shallow.skills.append("SQL")
deep.skills.append("Git")
print(original.skills) # ['Python', 'SQL']
print(deep.skills) # ['Python', 'Git']
类可以实现 __copy__() 和 __deepcopy__() 自定义复制行为。例如,数据库连接、文件句柄、线程锁等外部资源通常不适合被机械地深拷贝,应由类明确决定共享、重建或禁止复制。
常见误区
二维列表的乘法初始化
以下写法不会创建三个相互独立的内层列表:
matrix = [[0] * 3] * 3
matrix[0][0] = 1
print(matrix)
# [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
三个位置引用的是同一个内层列表。应使用列表推导式分别创建:
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1
print(matrix)
# [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
切片不是深拷贝
original[:] 只复制列表外层。列表包含嵌套列表、字典或类实例时,这些对象仍然共享。
深拷贝不一定适合所有对象
深拷贝不是默认最安全的选择。它可能:
- 复制大量数据,消耗更多时间和内存;
- 复制本应共享的状态,破坏对象之间的关系;
- 无法合理复制文件、网络连接、锁等外部资源;
- 让程序依赖隐式复制,而不是明确的数据所有权。
如何选择
- 希望多个变量操作同一状态:直接赋值;
- 只会增加、删除或替换外层元素,嵌套对象可以共享:浅拷贝;
- 副本需要独立修改嵌套可变对象:深拷贝;
- 数据规模较大或对象包含外部资源:优先设计明确的复制方法,只复制真正需要的字段;
- 数据本身适合不可变设计:可以使用不可变对象,减少复制和共享状态带来的问题。
选择复制方式的核心不是嵌套层数,而是副本和原对象之间是否允许共享可变状态。