Python 深拷贝实现方法详解
引言:从"复印件"说起
想象一个生活场景:你有一份非常重要的文件,需要交给同事填写。为了不弄丢原件,你会怎么做?
- 浅拷贝:你直接把原件的"复印件"给了同事。但这份复印件有个问题——文件上贴了一张便利贴,便利贴还是原来的那张。同事在便利贴上写了字,你的原件上也能看到。
- 深拷贝:你不仅复印了文件本身,还把便利贴也复印了一份新的。同事拿到的是完全独立的副本,无论他怎么修改,都不会影响你的原件。
Python 中的拷贝(Copy)正是如此:
- 浅拷贝(Shallow Copy):只复制对象本身,对象内部的引用(比如列表里的元素、字典里的值)还是共享的。
- 深拷贝(Deep Copy):递归地复制对象及其所有子对象,生成一个完全独立的新对象。
为什么需要深拷贝?看一个真实开发中的例子:
# 场景:员工信息管理
employee = {
"name": "张三",
"skills": ["Python", "SQL"], # 列表是可变对象
"address": {"city": "北京", "district": "朝阳区"} # 字典也是可变对象
}
# 浅拷贝:只复制最外层
import copy
shallow_emp = copy.copy(employee)
shallow_emp["skills"].append("Java") # 修改技能列表
print(employee["skills"]) # ['Python', 'SQL', 'Java'] —— 原对象被影响了!
# 深拷贝:完全独立
deep_emp = copy.deepcopy(employee)
deep_emp["skills"].append("Go")
print(employee["skills"]) # ['Python', 'SQL', 'Java'] —— 原对象安全如果你希望"复制出来的对象和原对象完全无关",就需要深拷贝。本文将系统讲解 Python 中所有主流的深拷贝实现方式。
一、深拷贝 vs 浅拷贝:核心区别
在深入各种实现方法之前,先彻底理解两者的区别。
1.1 内存结构对比
import copy
# 原始对象:包含可变子对象
original = {
"name": "李四",
"hobbies": ["读书", "跑步"],
"info": {"age": 25, "city": "上海"}
}
# 浅拷贝
shallow = copy.copy(original)
# 深拷贝
deep = copy.deepcopy(original)
print("=== 内存地址对比 ===")
print(f"original 地址: {id(original)}")
print(f"shallow 地址: {id(shallow)}") # 新地址,但内部子对象共享
print(f"deep 地址: {id(deep)}") # 全新地址,内部子对象也是新的
print(f"\noriginal['hobbies'] 地址: {id(original['hobbies'])}")
print(f"shallow['hobbies'] 地址: {id(shallow['hobbies'])}") # 相同!共享引用
print(f"deep['hobbies'] 地址: {id(deep['hobbies'])}") # 不同!独立副本输出结果:
=== 内存地址对比 ===
original 地址: 2561234567890
shallow 地址: 2561234568000 # 新对象,但...
deep 地址: 2561234568110 # 全新对象
original['hobbies'] 地址: 2561234568220
shallow['hobbies'] 地址: 2561234568220 # 和 original 相同!
deep['hobbies'] 地址: 2561234568330 # 全新地址1.2 行为差异验证
import copy
original = {"tags": ["a", "b"], "meta": {"count": 1}}
shallow = copy.copy(original)
deep = copy.deepcopy(original)
# 修改浅拷贝的子对象
shallow["tags"].append("c")
shallow["meta"]["count"] = 999
print("原对象:", original)
# {'tags': ['a', 'b', 'c'], 'meta': {'count': 999}} —— 被污染了!
# 深拷贝不受影响
deep["tags"].append("d")
deep["meta"]["count"] = 888
print("深拷贝修改后,原对象:", original)
# {'tags': ['a', 'b', 'c'], 'meta': {'count': 999}} —— 不再变化核心结论:
| 特性 | 浅拷贝 (copy.copy) | 深拷贝 (copy.deepcopy) |
|---|---|---|
| 最外层对象 | 新对象 | 新对象 |
| 内部可变子对象 | 共享引用 | 递归复制 |
| 修改子对象是否影响原对象 | 是 | 否 |
| 性能开销 | 小 | 大 |
| 适用场景 | 对象内部全是不可变类型 | 对象嵌套了可变类型 |
二、标准库方案:copy.deepcopy
copy.deepcopy 是 Python 标准库提供的官方深拷贝方案,也是最通用、最推荐的方式。
2.1 核心原理
copy.deepcopy 的工作流程:
- 遍历对象的所有属性/元素
- 对每个子对象递归执行深拷贝
- 使用
memo字典记录已拷贝的对象,处理循环引用 - 返回完全独立的新对象
2.2 基础用法与测试
import copy
# ========== 测试 1:嵌套字典 ==========
original = {
"user": {
"name": "王五",
"contacts": ["13800138000", "wangwu@example.com"]
},
"settings": {"theme": "dark", "notifications": True}
}
copied = copy.deepcopy(original)
# 修改拷贝对象的嵌套内容
copied["user"]["contacts"].append("13900139000")
copied["settings"]["theme"] = "light"
print("原对象:", original["user"]["contacts"])
# ['13800138000', 'wangwu@example.com'] —— 未受影响
print("拷贝对象:", copied["user"]["contacts"])
# ['13800138000', 'wangwu@example.com', '13900139000']
# ========== 测试 2:嵌套列表 ==========
matrix = [[1, 2, 3], [4, 5, 6]]
matrix_copy = copy.deepcopy(matrix)
matrix_copy[0][0] = 999
print("原矩阵:", matrix[0]) # [1, 2, 3] —— 未受影响
print("拷贝矩阵:", matrix_copy[0]) # [999, 2, 3]
# ========== 测试 3:自定义对象 ==========
class Address:
def __init__(self, city, street):
self.city = city
self.street = street
def __repr__(self):
return f"Address({self.city}, {self.street})"
class Person:
def __init__(self, name, address):
self.name = name
self.address = address
def __repr__(self):
return f"Person({self.name}, {self.address})"
person = Person("赵六", Address("深圳", "南山大道"))
person_copy = copy.deepcopy(person)
person_copy.address.city = "广州"
person_copy.name = "赵七"
print("原对象:", person) # Person(赵六, Address(深圳, 南山大道))
print("拷贝对象:", person_copy) # Person(赵七, Address(广州, 南山大道))2.3 处理循环引用
copy.deepcopy 能自动处理循环引用(对象 A 引用对象 B,对象 B 又引用对象 A):
import copy
# 构造循环引用
a = {"name": "A"}
b = {"name": "B", "friend": a}
a["friend"] = b # a 引用 b,b 引用 a
# 深拷贝不会死循环
a_copy = copy.deepcopy(a)
print(a_copy["friend"]["friend"] is a_copy) # True,循环结构被正确复制2.4 优缺点分析
| 优点 | 缺点 |
|---|---|
| 标准库自带,无需安装 | 性能开销最大 |
| 支持几乎所有 Python 对象 | 无法拷贝文件句柄、网络连接等系统资源 |
| 自动处理循环引用 | 深度过深时可能栈溢出 |
可通过 __deepcopy__ 自定义行为 | 对大型嵌套对象速度较慢 |
2.5 适用场景
- 通用场景,不确定对象内部结构时
- 自定义类实例的拷贝
- 需要处理循环引用的复杂数据结构
三、JSON 序列化法:json.dumps + json.loads
利用 JSON 序列化/反序列化实现深拷贝,是一种简单直接的"土办法"。
3.1 核心原理
json.dumps(obj):将对象序列化为 JSON 字符串(此时与原对象完全脱离)json.loads(json_str):将字符串解析回 Python 对象(生成全新对象)
3.2 代码示例与测试
import json
# ========== 测试:嵌套字典 ==========
original = {
"name": "孙七",
"scores": [90, 85, 92],
"details": {
"age": 28,
"hobbies": ["游泳", "摄影"]
}
}
# JSON 深拷贝
copied = json.loads(json.dumps(original))
# 修改拷贝对象
copied["scores"].append(100)
copied["details"]["hobbies"].append("旅行")
print("原对象:", original["scores"]) # [90, 85, 92]
print("拷贝对象:", copied["scores"]) # [90, 85, 92, 100]
print("原对象:", original["details"]["hobbies"]) # ['游泳', '摄影']
print("拷贝对象:", copied["details"]["hobbies"]) # ['游泳', '摄影', '旅行']3.3 优缺点分析
| 优点 | 缺点 |
|---|---|
| 代码极简,一行搞定 | 仅支持 JSON 兼容类型:dict, list, str, int, float, bool, None |
| 天然跨语言(生成 JSON) | 不支持自定义类、datetime、set、tuple 等 |
| 不需要额外导入 copy 模块 | 会丢失类型信息(如 tuple 变 list,int 键变 str 键) |
| 性能通常比 deepcopy 好 | 无法处理循环引用(会无限递归报错) |
3.4 类型丢失演示
import json
original = {
1: "数字键", # int 键
(1, 2): "元组键", # tuple 键 —— 会报错!JSON 不支持
"data": (1, 2, 3), # tuple 值
"numbers": {1, 2, 3}, # set —— 会报错!JSON 不支持
}
# 尝试 JSON 拷贝会报错:
# TypeError: keys must be str, int, float, bool or None, not tuple
# 修正后的版本
original = {
1: "数字键", # int 键
"data": (1, 2, 3), # tuple 值
}
copied = json.loads(json.dumps(original))
print(type(list(copied.keys())[0])) # <class 'str'> —— int 键变成了 str!
print(type(copied["data"])) # <class 'list'> —— tuple 变成了 list!3.5 适用场景
- 纯 JSON 数据(API 响应、配置文件)的拷贝
- 需要跨语言/跨系统传输数据的场景
- 对象结构简单,全是 JSON 兼容类型
四、序列化库进阶:pickle 与 msgpack
4.1 pickle:Python 专属序列化
pickle 是 Python 专用的二进制序列化协议,支持几乎所有 Python 对象。
import pickle
# ========== 测试:自定义类 ==========
class Config:
def __init__(self, name, values):
self.name = name
self.values = values
def __repr__(self):
return f"Config({self.name}, {self.values})"
original = Config("数据库配置", {"host": "localhost", "ports": [3306, 5432]})
# pickle 深拷贝
copied = pickle.loads(pickle.dumps(original))
copied.values["ports"].append(6379)
copied.name = "缓存配置"
print("原对象:", original) # Config(数据库配置, {'host': 'localhost', 'ports': [3306, 5432]})
print("拷贝对象:", copied) # Config(缓存配置, {'host': 'localhost', 'ports': [3306, 5432, 6379]})pickle 的局限:
- 仅 Python 可用,不跨语言
- 存在安全风险(不要反序列化不可信数据)
- 不同 Python 版本间可能不兼容
4.2 msgpack:高性能二进制序列化
msgpack 是跨语言的二进制序列化格式,比 JSON 更快、更紧凑。
pip install msgpackimport msgpack
# ========== 测试 ==========
original = {
"name": "周八",
"tags": ["admin", "developer"],
"meta": {"level": 3, "active": True}
}
# msgpack 深拷贝
copied = msgpack.unpackb(msgpack.packb(original), raw=False)
copied["tags"].append("owner")
copied["meta"]["level"] = 5
print("原对象:", original["tags"]) # ['admin', 'developer']
print("拷贝对象:", copied["tags"]) # ['admin', 'developer', 'owner']| 特性 | pickle | msgpack |
|---|---|---|
| 跨语言 | 否(仅 Python) | 是(多语言支持) |
| 速度 | 中等 | 快 |
| 支持类型 | 几乎所有 Python 对象 | JSON 兼容类型 + bytes |
| 安全性 | 低(可执行任意代码) | 较高 |
五、第三方库方案
5.1 ujson / orjson:更快的 JSON
标准库 json 的 C 语言加速版,API 兼容但速度更快。
pip install ujson
# 或
pip install orjsonimport ujson # 或 import orjson
original = {"items": [1, 2, 3], "config": {"debug": False}}
# 用法与标准 json 完全一致
copied = ujson.loads(ujson.dumps(original))
copied["items"].append(4)
print("原对象:", original["items"]) # [1, 2, 3]
print("拷贝对象:", copied["items"]) # [1, 2, 3, 4]orjson 额外优势:原生支持 datetime、UUID、numpy 数组等类型的序列化。
5.2 copyreg / __deepcopy__:自定义深拷贝行为
当 copy.deepcopy 默认行为不满足需求时,可以为自定义类定义 __deepcopy__ 方法。
import copy
class DatabaseConnection:
"""模拟数据库连接:连接本身不可拷贝,但配置可以"""
def __init__(self, host, credentials):
self.host = host
self.credentials = credentials # 敏感信息
self._connection = f"连接对象-{host}" # 模拟不可拷贝的资源
def __deepcopy__(self, memo):
# 自定义深拷贝:不复制连接,重新建立
new_obj = DatabaseConnection(
host=self.host,
credentials=copy.deepcopy(self.credentials, memo)
)
memo[id(self)] = new_obj
return new_obj
original = DatabaseConnection("db.example.com", {"user": "admin", "pass": "secret"})
copied = copy.deepcopy(original)
print(copied.host) # db.example.com
print(copied._connection) # 连接对象-db.example.com(重新创建,不是拷贝)
print(copied.credentials is not original.credentials) # True六、手动实现:递归拷贝
理解原理后,可以手写一个简易深拷贝,加深理解。
def manual_deepcopy(obj, memo=None):
"""
手动实现简易深拷贝
参数:
obj: 要拷贝的对象
memo: 用于处理循环引用的备忘录字典
"""
if memo is None:
memo = {}
# 已拷贝过,直接返回(处理循环引用)
obj_id = id(obj)
if obj_id in memo:
return memo[obj_id]
# 不可变类型直接返回
if isinstance(obj, (int, float, str, bool, type(None))):
return obj
# 列表
if isinstance(obj, list):
new_list = []
memo[obj_id] = new_list
for item in obj:
new_list.append(manual_deepcopy(item, memo))
return new_list
# 字典
if isinstance(obj, dict):
new_dict = {}
memo[obj_id] = new_dict
for key, value in obj.items():
new_dict[manual_deepcopy(key, memo)] = manual_deepcopy(value, memo)
return new_dict
# 集合
if isinstance(obj, set):
new_set = set()
memo[obj_id] = new_set
for item in obj:
new_set.add(manual_deepcopy(item, memo))
return new_set
# 其他类型(简化处理,实际应更完善)
return obj
# 测试
original = {"a": [1, 2, {"b": [3, 4]}]}
copied = manual_deepcopy(original)
copied["a"][2]["b"].append(5)
print("原对象:", original["a"][2]["b"]) # [3, 4]
print("拷贝对象:", copied["a"][2]["b"]) # [3, 4, 5]注意:这只是教学示例,生产环境请使用
copy.deepcopy,它处理了更多边界情况。
七、常见坑点与注意事项
7.1 性能陷阱
深拷贝是递归操作,对象嵌套越深、数据量越大,性能开销越大。
import copy
import time
import json
# 构造大型嵌套对象
large_data = {"level": 0}
current = large_data
for i in range(1000):
current["child"] = {"level": i + 1, "data": list(range(100))}
current = current["child"]
# 测试 deepcopy 耗时
start = time.time()
copy1 = copy.deepcopy(large_data)
print(f"copy.deepcopy: {time.time() - start:.3f}秒")
# 测试 json 耗时(如果适用)
start = time.time()
copy2 = json.loads(json.dumps(large_data))
print(f"json 拷贝: {time.time() - start:.3f}秒")优化建议:
- 如果对象内部全是不可变类型,用浅拷贝即可
- 考虑只深拷贝需要修改的部分
- 使用
__deepcopy__自定义精简拷贝逻辑
7.2 不可拷贝的对象
以下对象无法深拷贝,会抛出 TypeError:
import copy
import threading
# 文件对象
with open("test.txt", "w") as f:
try:
copy.deepcopy(f)
except TypeError as e:
print(f"文件对象不可拷贝: {e}")
# 线程锁
lock = threading.Lock()
try:
copy.deepcopy(lock)
except TypeError as e:
print(f"线程锁不可拷贝: {e}")
# 生成器
gen = (x for x in range(10))
try:
copy.deepcopy(gen)
except TypeError as e:
print(f"生成器不可拷贝: {e}")7.3 循环引用导致的问题
虽然 copy.deepcopy 能处理循环引用,但 JSON 方法会失败:
import json
a = {"name": "A"}
b = {"name": "B", "ref": a}
a["ref"] = b
# JSON 方法会无限递归
try:
json.dumps(a)
except ValueError as e:
print(f"JSON 无法处理循环引用: {e}")
# deepcopy 可以
import copy
c = copy.deepcopy(a) # 正常工作7.4 类型信息丢失
JSON 方法会丢失类型信息:
| 原类型 | JSON 拷贝后 |
|---|---|
tuple | list |
set | 报错(不支持) |
datetime | 报错(不支持) |
int 字典键 | str 键 |
| 自定义类 | 报错(不支持) |
7.5 深拷贝不是万能的
深拷贝解决的是"对象图独立"问题,但以下情况仍需注意:
import copy
# 场景 1:类属性(类级别共享)
class Config:
defaults = {"timeout": 30} # 类属性,所有实例共享
c1 = Config()
c2 = copy.deepcopy(c1)
c2.defaults["timeout"] = 60
print(Config.defaults) # {'timeout': 60} —— 类属性被改了!
# 场景 2:单例模式
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = copy.deepcopy(s1)
print(s1 is s2) # True —— deepcopy 对 __new__ 有特殊处理八、方案选型速查表
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 通用场景,对象结构未知 | copy.deepcopy | 最全面,标准库支持 |
| 纯 JSON 数据(API/配置) | json.loads/dumps | 简单快速,天然跨语言 |
| 自定义类实例 | copy.deepcopy | 支持 __deepcopy__ 自定义 |
| 需要跨语言传输 | msgpack / json | 序列化格式通用 |
| 高性能要求,纯数据 | orjson / msgpack | C 实现,速度最快 |
| 包含循环引用 | copy.deepcopy | 唯一能安全处理循环引用 |
| 教学/理解原理 | 手动实现 | 深入理解递归和 memo 机制 |
九、总结
Python 深拷贝的核心要点:
copy.deepcopy是首选:标准库、通用、安全,能处理循环引用和自定义类- JSON 法适合简单数据:纯 JSON 兼容类型时,代码更简洁、性能更好
- pickle/msgpack 适合特定场景:需要序列化存储或跨语言传输时
- 性能敏感场景要谨慎:深拷贝是 O(n) 递归操作,大对象要考虑优化
记住一个原则:只在必要时深拷贝。如果对象内部全是不可变类型(数字、字符串、元组),浅拷贝就足够了。
本文示例代码均基于 Python 3.8+ 测试通过,可直接复制运行。第三方库需先通过
pip install安装。