一文读懂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. 深拷贝:递归复制整个对象树,创建完全独立的副本

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

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

相关推荐
uzong8 分钟前
软件架构指南 Software Architecture Guide
后端
又是忙碌的一天8 分钟前
SpringBoot 创建及登录、拦截器
java·spring boot·后端
勇哥java实战分享1 小时前
短信平台 Pro 版本 ,比开源版本更强大
后端
学历真的很重要1 小时前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
计算机毕设VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
上进小菜猪1 小时前
基于 YOLOv8 的智能杂草检测识别实战 [目标检测完整源码]
后端
wzfj123452 小时前
ssh 远程pc如何不用每次都输入密码
github
韩师傅2 小时前
前端开发消亡史:AI也无法掩盖没有设计创造力的真相
前端·人工智能·后端
栈与堆3 小时前
LeetCode-1-两数之和
java·数据结构·后端·python·算法·leetcode·rust
superman超哥3 小时前
双端迭代器(DoubleEndedIterator):Rust双向遍历的优雅实现
开发语言·后端·rust·双端迭代器·rust双向遍历