查看: 132|回复: 3

Python深拷贝和浅拷贝区别、实现方式与性能对

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在Python里处理列表、字典、自定义类实例时,赋值、浅拷贝和深拷贝的内存语义不同。赋值只是新增引用,浅拷贝只复制外层,深拷贝递归复制所有层级。理解这三者,重点要盯住内部可变对象是否共享,以及修改后原对象是否被带动。

一、赋值:多个变量指向同一对象
  1. a = [1, 2, [3, 4]]
  2. b = a
  3. b[0] = 100
  4. print(a)
复制代码
输出是[100, 2, [3, 4]]。这里a和b指向同一内存地址,改b等于改a。这个操作不是复制,排查数据被意外修改时,先看是否存在直接赋值共享。

二、浅拷贝:外层新对象,内部可变对象仍共享
浅拷贝的常见入口有list()、dict()、set()、切片lst[:]以及copy.copy()。它会给外层容器创建新对象,但内部可变元素只复制引用。
  1. import copy
  2. a = [1, 2, [3, 4]]
  3. b = copy.copy(a)
  4. b[0] = 100
  5. b[2][0] = 300
  6. print(a)
复制代码
输出[1, 2, [300, 4]]。b[0]=100只改外层不可变元素,不影响a;b[2][0]=300改的是共享的内部列表,所以a也变。类似的切片和字典浅拷贝示例:
  1. a = [1, [2, 3]]
  2. b = a[:]
  3. b[1][0] = 200
  4. print(a)
  5. a = {'k1': [1, 2]}
  6. b = copy.copy(a)
  7. b['k1'].append(3)
  8. print(a)
复制代码
分别输出[1, [200, 3]]和{'k1': [1, 2, 3]}。也就是说,浅拷贝后修改外层键值通常安全,修改嵌套列表、字典等内部可变对象会波及原对象。

三、深拷贝:递归复制所有层级
copy.deepcopy()会递归复制对象及其嵌套结构,生成完全独立的副本。新对象和原对象不共享内部可变对象。
  1. import copy
  2. a = [1, 2, [3, 4]]
  3. b = copy.deepcopy(a)
  4. b[0] = 100
  5. b[2][0] = 300
  6. print(a)
复制代码
输出仍是[1, 2, [3, 4]]。嵌套字典同理:
  1. a = {'k1': [1, 2], 'k2': {'inner': 3}}
  2. b = copy.deepcopy(a)
  3. b['k1'].append(3)
  4. b['k2']['inner'] = 300
  5. print(a)
复制代码
原字典保持{'k1': [1, 2], 'k2': {'inner': 3}}。深拷贝适合需要完全独立副本的场景,但递归复制越深、对象越大,开销越高。

四、三者区别归纳
赋值不创建新对象,只创建引用,内存完全共享,修改一个变量会影响所有引用,性能开销极低。
浅拷贝创建外层新对象,内部可变对象共享;修改外层通常不影响原对象,修改内部可变对象会影响原对象;开销中等。
深拷贝创建所有层级的新对象,内部无共享;修改副本不影响原对象;开销高。
实际选择时,核心判断不是“容器类型是什么”,而是“副本内部是否还有会被修改的可变对象”。

五、特殊对象与边界情况
纯不可变对象如数字、字符串、不含可变元素的元组,浅拷贝和深拷贝通常直接返回原对象,因为不可变对象无法被修改。但元组里嵌套列表这类结构不能只看外层类型,内部可变对象仍可能带来共享问题,需要结合copy行为实际验证。
自定义类默认拷贝行为由copy.copy()和copy.deepcopy()的默认逻辑处理;如果需要控制复制细节,可实现__copy__()和__deepcopy__()。下面示例展示默认浅拷贝下内部列表仍被共享:
  1. import copy
  2. class MyClass:
  3.     def __init__(self, value):
  4.         self.value = value
  5. obj1 = MyClass([1, 2])
  6. obj2 = copy.copy(obj1)
  7. obj2.value.append(3)
  8. print(obj1.value)
复制代码
输出[1, 2, 3]。原因是浅拷贝只复制了实例外层,value列表仍指向同一对象。
循环引用也是深拷贝要处理的重点。深拷贝会自动处理循环引用,避免无限递归:
  1. import copy
  2. a = [1]
  3. a.append(a)
  4. b = copy.deepcopy(a)
  5. print(b[1] is b)
复制代码
输出True,说明副本中的循环引用关系被正确保留。

六、应用场景
浅拷贝适合只改外层结构、内部对象不会被修改的场景,也适合性能敏感且只需一层副本的情况。例如复制配置字典后只替换server字段,内部options在确定不修改时可继续共享:
  1. import copy
  2. config = {'server': 'localhost', 'options': {'timeout': 30}}
  3. new_config = copy.copy(config)
  4. new_config['server'] = 'remote'
复制代码
深拷贝适合需要完全独立副本的场景,例如游戏角色状态备份,避免备份数据与当前角色共享inventory:
  1. import copy
  2. character = {'name': 'Hero', 'inventory': [{'item': 'sword', 'level': 5}]}
  3. backup = copy.deepcopy(character)
复制代码

七、性能对比与调试建议
深拷贝需要递归复制所有层级,性能通常显著低于浅拷贝。可用timeit对实际数据结构做对比:
  1. import timeit
  2. import copy
  3. data = {'a': [1] * 1000}
  4. for _ in range(100):
  5.     data = {'nested': data}
  6. print('浅拷贝耗时:', timeit.timeit(lambda: copy.copy(data), number=100))
  7. print('深拷贝耗时:', timeit.timeit(lambda: copy.deepcopy(data), number=100))
复制代码
这段代码只是演示测量方式,实际耗时取决于嵌套深度、对象数量和机器环境。若发现修改副本后原数据变化,优先检查是否用了赋值或浅拷贝;若深拷贝导致接口变慢,则要评估是否真的需要全量独立副本。

总结:赋值是共享引用;浅拷贝复制外层但共享内部可变对象;深拷贝递归复制所有层级。多数场景优先浅拷贝,只有确实需要完全独立副本时再用深拷贝;纯不可变对象可直接赋值;自定义类按需实现__copy__()和__deepcopy__()。围绕copy模块和嵌套可变对象设计数据结构,能减少意外修改和性能问题。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python深拷贝和浅拷贝区别、实现方式与性能对

这篇讲得很清楚,把赋值、浅拷贝、深拷贝的关键差异落在“内部可变对象是否共享”上,比只记容器类型实用得多。尤其是浅拷贝后改外层通常没事、改嵌套列表或字典会波及原对象这一点,排查数据被意外修改时很容易踩坑。循环引用和自定义类那两个例子也补得及时,说明深拷贝不是简单套一层。性能部分用 timeit 实测的思路也稳妥,实际项目里确实要先判断是否真的需要完全独立副本,能浅拷贝就别盲目深拷贝。整体适合当速查和避坑参考。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python深拷贝和浅拷贝区别、实现方式与性能对

写得很系统,把赋值、浅拷贝、深拷贝的内存语义和修改影响讲清楚了。几个例子特别实用,尤其自定义类默认浅拷贝共享内部列表、循环引用 deepcopy 后 b[1] is b 为 True 这两段,很能说明问题。性能部分用 timeit 演示测量方式而不是给绝对耗时,也很稳妥。实际排查时按“先看有没有直接赋值,再看浅拷贝内部可变对象”这个顺序很顺手。补充一点:自定义 __deepcopy__ 时记得处理 memo 参数,否则遇到循环引用可能出问题;不过帖子里已经点到循环引用,整体已经很完整了。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python深拷贝和浅拷贝区别、实现方式与性能对

楼主这篇梳理得很清楚,赋值、浅拷贝、深拷贝三层语义用嵌套列表一演示基本就立住了,尤其是“是否共享内部可变对象”这个判断核心,比单纯记容器类型实用多了。 几个自己踩过坑的点,跟帖补充一下: 一是元组那块确实容易误判。外层是不含可变元素的元组时,浅拷贝和深拷贝往往直接返回原对象,但元组里套了列表,深拷贝下去还是会产生新列表;如果元组里套的全是不可变对象,连深拷贝都可能直接返回原元组。所以真不能只看外层类型,最好拿 is 比较一下副本和原对象、再比较一下内部元素,实际验证最稳。 二是循环引用那个例子很直观,b[1] is b 为 True 说明副本内部关系被保留了。不过也提醒一下,既然深拷贝要维护 memo 表来处理循环引用和重复引用,开销自然比浅拷贝大不少,遇到性能敏感路径时先确认是否真的需要全量独立副本,很多时候只复制关心的那几层就够了。 三是性能对比那段,timeit 测出来的数字受缓存、GC、对象生命周期影响挺大,建议正式对比前先跑几轮预热,或者用 repeat 取最小值,不然深拷贝和浅拷贝的差距可能被噪声干扰。另外如果只是配置字典里替换某个顶层字段,浅拷贝后不动内部 options 确实够用,这点楼主举例举得很贴切。 自定义类实现 __copy__ 和 __deepcopy__ 这块,如果类里有资源句柄或者缓存字典,默认深拷贝可能会把不必要的东西也复制一遍,按需控制反而更安全。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-14 03:54 , Processed in 0.023047 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部