Python进阶之-深拷贝与浅拷贝写在前面很多刚接触Python的小白在处理列表、字典等数据时经常会遇到一个灵异事件我明明只改了A列表怎么B列表也跟着变了 其实这都是Python的“赋值”、“浅拷贝”和“深拷贝”在捣鬼。今天我们就用大白话把这哥仨掰扯得明明白白作为Python进阶的必学知识点深拷贝与浅拷贝直接关系到数据操作的安全性也是面试中的高频考点。本文将从底层引用原理出发结合可运行代码示例带你彻底搞懂二者的差异、运行逻辑与适用场景。文章目录Python进阶之-深拷贝与浅拷贝一、核心前置知识1.1 可变类型 vs 不可变类型1.2 Python赋值的本质引用传递二、浅拷贝只拷贝最外层2.1 可变类型的浅拷贝2.2 不可变类型的浅拷贝三、深拷贝递归全层级拷贝3.1 可变类型的深拷贝3.2 不可变类型的深拷贝四、一张表总结三者核心区别五、进阶话题5.1 混合类型嵌套中包含不可变类型5.2 自定义对象的拷贝5.3 循环引用的处理5.4 性能考量5.5 一键验证模板六、实战场景与避坑指南6.1 适用场景6.2 常见避坑提醒结语一、核心前置知识在讲解拷贝之前我们先理清两个必备的前置知识这是理解深浅拷贝的前提。1.1 可变类型 vs 不可变类型Python中数据类型分为两类划分依据是在内存地址不变的前提下对象的内容能否被修改。可变类型列表、字典、集合。内存地址不变时内部元素可以修改。不可变类型整型、浮点型、字符串、布尔型、元组。内容一旦创建就无法修改修改时会生成新的内存对象。总结深浅拷贝的区别就是拷贝的层级多与少。深拷贝拷贝的多浅拷贝拷贝的少。它们一般都用来操作可变类型。在Python中深浅拷贝需要借用 copy 模块浅拷贝copy.copy()深拷贝copy.deepcopy()# 不可变类型修改即生成新对象a10print(fid(a) -{id(a)})# 初始地址a20print(fid(a) -{id(a)})# 地址改变生成了新对象# 可变类型修改不改变地址list1[11,22,33]print(fid(list1) -{id(list1)})# 初始地址list1[1]200print(fid(list1) -{id(list1)})# 地址不变内容已修改1.2 Python赋值的本质引用传递Python中的赋值本质上不是“复制值”而是传递内存地址。赋值操作只是给同一块内存多起了一个别名并不会创建新的独立对象。# python的赋值操作属于引用赋值(eg:b是a的别名, 形参是实参的别名)defdm01_普通赋值():# 1. python中的赋值操作, 属于引用赋值 (把a的地址赋值给b)# 2. b是a的别名, b和a都指向相同的内存空间a10baprint(id(a)--,id(a))# 这三个输出的地址是一模一样的print(id(b)--,id(b))print(id(10)--,id(10))print(-*31)# 3. 同理列表的嵌套赋值也是引用赋值c和d指向相同的内存空间a[1,2,3]b[11,22,33]c[a,b]dcprint(id(c)--,id(c))# 这两个输出的地址也是一样的print(id(d)--,id(d))dm01_普通赋值()也正因为如此对于可变类型通过一个别名修改内容另一个别名访问到的数据也会同步变化——这就是我们需要“拷贝”的原因。小白理解张三有个外号叫“三哥”。你打了张三一巴掌“三哥”也会觉得疼因为他们本来就是同一个人。普通赋值就是这样。二、浅拷贝只拷贝最外层浅拷贝会创建一个新的对象但是对于对象内部的元素只是拷贝了它们的引用地址。浅拷贝对应copy模块中的copy.copy()函数核心规则可以概括为只拷贝对象的最外层容器内层元素依然共享内存引用。2.1 可变类型的浅拷贝对于嵌套的可变结构比如列表里嵌套列表浅拷贝只会重新创建最外层的容器内层的子对象依然和原对象共用同一块内存。importcopy# 需求1: 浅拷贝可变类型: 只拷贝第1层数据, 深层次数据不拷贝defdm02_浅拷贝可变类型():a[1,2,3]b[11,22,33]c[6,7,a,b]# 测试1: 外层地址dcopy.copy(c)print(id(c)--,id(c))print(id(d)--,id(d))print(结论: id(c)和id(d)值不一样, 说明浅拷贝把【第1层(最外面一层)】拷贝了一份新的。)print(-*31)# 测试2: 内层地址print(id(c[2]))# c[2] 就是 aprint(id(a))print(结论: id(c[2])和id(a)值一样, 说明浅拷贝【第2层的数据】并没有拷贝还是共享的)print(-*31)# 测试3: 修改内层数据看效果a[2]22print(c-,c)# [6, 7, [1, 2, 22], [11, 22, 33]]print(d-,d)# [6, 7, [1, 2, 22], [11, 22, 33]]# 结论因为a变了而c和d里面的第二层都指向a所以c和d都变了这就是浅拷贝的坑dm02_浅拷贝可变类型()拓展除了copy.copy()列表切片list[:]、list()构造、dict()构造、set.copy()等常用写法本质也都是浅拷贝。2.2 不可变类型的浅拷贝如果拷贝的对象本身是不可变类型浅拷贝不会创建新对象会直接返回原对象的引用。原因很简单不可变类型本身无法修改内容共享引用不会产生任何副作用Python 解释器做了内存优化避免不必要的空间浪费。importcopy# 浅拷贝不可变类型: 不会给拷贝的对象c开辟新的内存空间, 而只是拷贝了这个对象的引用defdm03_浅拷贝不可变类型():# 不可变类型 a b c (元组是不可变类型)a(1,2,3)b(11,22,33)c(6,7,a,b)dcopy.copy(c)print(id(c)--,id(c))print(id(d)--,id(d))print(结论: id(c)和id(d)值一样, 说明c和d指向相同的内存空间)# 为什么会这样因为不可变类型本身值都是不能被修改的没有再浪费内存空间的必要# 所以程序员要copy不可变类型时Python解释器直接返回原对象的地址(别名/引用)主打一个省钱省内存dm03_浅拷贝不可变类型()三、深拷贝递归全层级拷贝深拷贝会创建一个全新的对象并且递归地把所有层级内部的对象也都拷贝一份。深拷贝对应copy模块中的copy.deepcopy()函数核心规则是递归遍历所有层级只要是可变类型就重新创建独立内存。拷贝完成后新对象与原对象完全独立无论怎么改互不影响3.1 可变类型的深拷贝对于嵌套可变结构深拷贝会逐层深入每一层可变容器都会重新创建彻底切断与原对象的引用关联。importcopy# 需求1: 深拷贝可变类型: 只要是可变类型, 每一层都会拷贝.defdm02_深拷贝可变类型():a[1,2,3]b[11,22,33]c[6,7,a,b]# 测试1: 外层地址dcopy.deepcopy(c)print(id(c)--,id(c))print(id(d)--,id(d))# 地址不同外层拷贝了print(-*31)# 测试2: 内层地址print(id(c[2]))# c[2]是aprint(id(a))# 说明c[2]和a还是同一个# 注意虽然c[2]和a一样但d[2]和a绝对不一样了深拷贝在d内部全新造了一个列表print(-*31)# 测试3: 修改内层数据看效果a[2]22print(c-,c)# [6, 7, [1, 2, 22], [11, 22, 33]]print(d-,d)# [6, 7, [1, 2, 3], [11, 22, 33]] --- d完全没有受到影响# 结论深拷贝连内部的列表都重新克隆了一份原数据a怎么改都影响不到d了。dm02_深拷贝可变类型()3.2 不可变类型的深拷贝对于纯不可变类型深拷贝与浅拷贝效果一致都会直接复用原对象引用。需要注意一个细节如果不可变容器比如元组内部嵌套了可变类型深拷贝依然会递归拷贝内层的可变元素保证最终副本完全独立。# 深拷贝不可变类型: 同浅拷贝一样不会开辟新内存只是拷贝引用defdm03_深拷贝不可变类型():# 不可变类型 a b ca(1,2,3)b(11,22,33)c(6,7,a,b)dcopy.deepcopy(c)print(id(c)--,id(c))print(id(d)--,id(d))print(结论: id(c)和id(d)值一样, 说明c和d指向相同的内存空间)# 原理同浅拷贝不可变类型不可变类型不能改没必要浪费内存重新造直接返回原地址。dm03_深拷贝不可变类型()四、一张表总结三者核心区别操作方式外层可变类型内层可变类型纯不可变类型数据联动程度直接赋值共享引用共享引用共享引用完全同步修改一处全影响浅拷贝copy.copy()创建新对象共享引用复用原对象修改内层数据会同步变化深拷贝copy.deepcopy()创建新对象创建新对象复用原对象完全独立互不影响列表切片list[:]、list()构造、dict()构造、set.copy()等常用写法本质也都是浅拷贝。浅拷贝不可变类型内存图解因为元组本身不可修改不存在 “改副本影响原数据” 的风险所以浅拷贝直接复用原对象地址效果和普通赋值完全一致。深拷贝可变类型内存图解深拷贝会递归复制所有层级的可变对象引用层外层c地址0x03和d地址0x04是独立地址数据层内层的列表也被完整拷贝生成了新的独立地址0x05、0x06和原对象彻底切断关联直观结论深拷贝是完完整整复制了全量数据两个对象完全独立修改任意层级都不会互相干扰五、进阶话题5.1 混合类型嵌套中包含不可变类型当嵌套结构中既有可变类型又有不可变类型时深拷贝的表现如何importcopy original[1,(2,3),[4,5]]copiedcopy.deepcopy(original)print(id(original[1]))# 地址1print(id(copied[1]))# 地址1不可变类型不复制print(id(original[2]))# 地址2print(id(copied[2]))# 地址3可变类型复制深拷贝只对可变类型进行递归复制不可变类型直接共享引用。5.2 自定义对象的拷贝对于自定义类默认的copy.copy()和copy.deepcopy()也会按照上述规则工作。如果需要自定义拷贝行为可以实现__copy__()和__deepcopy__()方法。importcopyclassMyClass:def__init__(self,data):self.datadata objMyClass([1,2,3])shallowcopy.copy(obj)# 浅拷贝 objdeepcopy.deepcopy(obj)# 深拷贝 obj5.3 循环引用的处理深拷贝能够正确处理循环引用不会陷入无限递归。importcopy a[1,2]b[a,3]a.append(b)# 形成循环引用a - b - accopy.deepcopy(a)# 正常完成不会死循环print(c)# [[1, 2, [...]], 3] ... 表示循环引用5.4 性能考量浅拷贝时间复杂度 O(n)n 是第一层元素个数。深拷贝时间复杂度 O(N)N 是所有可变元素的总数且递归复制会消耗更多内存。在性能敏感的场合应优先使用浅拷贝只有在确实需要完全隔离时才使用深拷贝。5.5 一键验证模板importcopydefanalyze_copy(data,copy_typedeep):分析拷贝行为的通用工具函数originaldata copiedcopy.copy(original)ifcopy_typeshallowelsecopy.deepcopy(original)print(f\n{copy_type.upper()}COPY ANALYSIS )print(fOriginal ID:{id(original)})print(fCopied ID:{id(copied)})# 检查第一个嵌套可变对象ifisinstance(original,(list,dict))andlen(original)0:ifisinstance(original,list):nestedoriginaliflen(original)0elseNonenested_copiedcopiediflen(copied)0elseNoneelse:# dictnestedlist(original.values())iflen(original)0elseNonenested_copiedlist(copied.values())iflen(copied)0elseNoneifnestedisnotNoneandisinstance(nested,(list,dict)):print(fNested object ID (original):{id(nested)})print(fNested object ID (copied):{id(nested_copied)})print(fNested objects are{identicalifid(nested)id(nested_copied)elseindependent})# 通过键或索引访问ifisinstance(original,list)andlen(original)0andisinstance(original,list):originalMODIFIED# 修改第一个嵌套列表的第一个元素elifisinstance(original,dict)andlen(original)0:first_keynext(iter(original))# 获取第一个键ifisinstance(original[first_key],list):original[first_key]MODIFIED# 修改第一个值列表的第一个元素elifisinstance(original[first_key],dict):first_nested_keynext(iter(original[first_key]))original[first_key][first_nested_key]MODIFIED# 修改嵌套字典的值print(fOriginal after modify:{original})print(fCopied after modify:{copied})# 使用示例test_data[[1,2],{key:[3,4]}]analyze_copy(test_data,shallow)analyze_copy(test_data,deep)六、实战场景与避坑指南场景推荐方式原因函数传参仅读取普通赋值零开销效率最高配置模板复用内部不变浅拷贝节省内存结构复用多线程/异步修改副本深拷贝避免竞态条件数据安全复制大型嵌套结构如JSON树⚠️ 谨慎使用可能导致内存爆炸或延迟复制含自引用对象必须用深拷贝copy.deepcopy 自动处理循环引用6.1 适用场景浅拷贝适用场景数据是单层扁平结构内层全是数字、字符串等不可变类型追求拷贝性能不需要完全独立的副本典型场景复制单层列表、扁平化的配置字典深拷贝适用场景数据是嵌套结构内层包含列表、字典等可变类型需要完全独立的副本修改副本不能污染原数据典型场景复制复杂配置项、深拷贝自定义类对象、传递嵌套业务数据6.2 常见避坑提醒不要误以为list()、列表切片就是“完全复制”它们都只是浅拷贝嵌套结构依然会联动不要不分场景滥用深拷贝复杂嵌套结构下深拷贝有遍历性能开销数据量大时会影响效率自定义类对象的拷贝规则一致浅拷贝只复制实例本身属性中的可变对象依然共享引用误区正解“深拷贝会复制所有数据包括数字、字符串”❌ 错误。不可变对象仍共享引用深拷贝只复制可变对象的层级“浅拷贝只对列表有效”❌ 错误。对 dict、set 同样适用copy.copy(my_dict)“深拷贝太慢永远别用”❌ 错误。在需要数据隔离的场景如测试、并发、缓存中它是唯一安全选择list.copy() 和 copy.copy(list) 不一样❌ 错误。二者完全等价都是浅拷贝“深拷贝会破坏原对象”❌ 错误。深拷贝是只读复制原对象毫发无损结语深拷贝与浅拷贝的核心差异本质就是拷贝的层级深度不同。理解了Python的引用传递机制再结合可变、不可变类型的特性就能轻松避开“改副本影响原数据”的常见坑根据业务场景选择最合适的拷贝方式写出更安全、更健壮的代码。