一文读懂copy与deepcopy的区别与实战应用

什么是Python中的拷贝?

在Python中,变量赋值实际上是创建了对象的引用,而非复制对象本身。当我们需要复制对象时,有两种方式:浅拷贝(shallow copy)深拷贝(deep copy)

浅拷贝与深拷贝的基本概念

  • 浅拷贝:只复制对象的第一层,内部嵌套的可变对象仍然是引用关系
  • 深拷贝:递归复制整个对象,包括所有嵌套的对象,创建完全独立的副本

浅拷贝详解

Python提供了多种创建浅拷贝的方式:

ini 复制代码
python
import copy

# 方式1:使用copy模块的copy()函数
原列表 = [1, 2, [3, 4]]
浅拷贝1 = copy.copy(原列表)

# 方式2:使用列表的copy()方法
浅拷贝2 = 原列表.copy()

# 方式3:使用切片操作
浅拷贝3 = 原列表[:]

# 方式4:使用list()构造函数
浅拷贝4 = list(原列表)

# 字典的浅拷贝
原字典 = {'a': 1, 'b': [2, 3]}
浅拷贝字典 = 原字典.copy()  # 或者 copy.copy(原字典)

浅拷贝的特点:修改嵌套对象会影响原对象

scss 复制代码
python
import copy

# 浅拷贝示例
原列表 = [[1, 2], [3, 4], 5]
浅拷贝 = copy.copy(原列表)

# 修改浅拷贝中的嵌套列表
浅拷贝[0][0] = 999

print(f"修改后的浅拷贝:{浅拷贝}")  # [[999, 2], [3, 4], 5]
print(f"原列表:{原列表}")         # [[999, 2], [3, 4], 5] - 原列表也被修改了!

# 但修改浅拷贝的第一层不会影响原列表
浅拷贝[2] = 888
print(f"再次修改后的浅拷贝:{浅拷贝}")  # [[999, 2], [3, 4], 888]
print(f"原列表:{原列表}")             # [[999, 2], [3, 4], 5] - 原列表不受影响

深拷贝详解

深拷贝使用copy.deepcopy()函数创建:

scss 复制代码
python
import copy

# 深拷贝示例
原列表 = [[1, 2], [3, 4], 5]
深拷贝 = copy.deepcopy(原列表)

# 修改深拷贝中的嵌套列表
深拷贝[0][0] = 999

print(f"修改后的深拷贝:{深拷贝}")  # [[999, 2], [3, 4], 5]
print(f"原列表:{原列表}")         # [[1, 2], [3, 4], 5] - 原列表不受影响!

深拷贝处理循环引用

深拷贝能够智能处理循环引用的情况:

python 复制代码
python
import copy

# 创建循环引用
循环列表 = [1, 2, 3]
循环列表.append(循环列表)  # 列表引用自身

# 深拷贝可以正确处理循环引用
深拷贝循环 = copy.deepcopy(循环列表)

print(f"循环列表:{id(循环列表)}")
print(f"循环列表中的自引用:{id(循环列表[3])}")
print(f"深拷贝:{id(深拷贝循环)}")
print(f"深拷贝中的自引用:{id(深拷贝循环[3])}")

性能对比

深拷贝与浅拷贝的性能差异:

python 复制代码
python
import copy
import time

# 创建大型嵌套列表
大列表 = [[i for i in range(1000)] for _ in range(100)]

# 测量浅拷贝性能
开始时间 = time.time()
浅拷贝 = copy.copy(大列表)
结束时间 = time.time()
print(f"浅拷贝耗时: {(结束时间-开始时间)*1000:.2f}毫秒")  # 通常<1毫秒

# 测量深拷贝性能
开始时间 = time.time()
深拷贝 = copy.deepcopy(大列表)
结束时间 = time.time()
print(f"深拷贝耗时: {(结束时间-开始时间)*1000:.2f}毫秒")  # 通常在10-50毫秒之间

实际应用场景

1. 函数参数中避免副作用

lua 复制代码
python
import copy

def 安全处理(数据列表):
    # 创建深拷贝避免修改原始数据
    工作副本 = copy.deepcopy(数据列表)
    # 对工作副本进行操作
    for i in range(len(工作副本)):
        if isinstance(工作副本[i], list):
            工作副本[i][0] *= 2
    return 工作副本

原始数据 = [[1, 2], [3, 4]]
处理后数据 = 安全处理(原始数据)
print(f"原始数据: {原始数据}")  # [[1, 2], [3, 4]] - 不变
print(f"处理后数据: {处理后数据}")  # [[2, 2], [6, 4]]

2. 缓存中间状态

python 复制代码
python
import copy

class 游戏状态:
    def __init__(self):
        self.玩家位置 = [0, 0]
        self.分数 = 0
        self.收集物品 = []
    
    def 保存检查点(self):
        return copy.deepcopy(self)
    
    def 从检查点恢复(self, 检查点):
        self.玩家位置 = 检查点.玩家位置
        self.分数 = 检查点.分数
        self.收集物品 = 检查点.收集物品

# 使用示例
游戏 = 游戏状态()
游戏.分数 = 100
游戏.收集物品 = ["钥匙", "宝石"]

# 保存检查点
检查点 = 游戏.保存检查点()

# 继续游戏
游戏.分数 = 150
游戏.收集物品.append("剑")

print(f"当前状态: 分数={游戏.分数}, 物品={游戏.收集物品}")
# 当前状态: 分数=150, 物品=['钥匙', '宝石', '剑']

# 从检查点恢复
游戏.从检查点恢复(检查点)
print(f"恢复后状态: 分数={游戏.分数}, 物品={游戏.收集物品}")
# 恢复后状态: 分数=100, 物品=['钥匙', '宝石']

总结

  1. 浅拷贝:只复制对象的第一层,内部嵌套的可变对象共享引用

    • 适合:对象结构简单或只需修改第一层时
    • 性能:快速,内存占用少
  2. 深拷贝:递归复制整个对象树,创建完全独立的副本

    • 适合:需要避免任何副作用,或对象有复杂嵌套结构时
    • 性能:较慢,内存占用较多

根据实际需求,选择合适的拷贝方式,可以有效避免意外的数据修改问题,提高代码的健壮性。

相关推荐
想用offer打牌2 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX3 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法4 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
草梅友仁5 小时前
墨梅博客 1.4.0 发布与开源动态 | 2026 年第 6 周草梅周报
开源·github·ai编程
Cobyte5 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
程序员侠客行6 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple6 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端