Python深拷贝与浅拷贝,很多人都理解错了

张开发
2026/5/23 12:31:52 15 分钟阅读
Python深拷贝与浅拷贝,很多人都理解错了
目录一、从一个简单的例子开始二、Python 中的对象与引用三、浅拷贝Shallow Copy1. 什么是浅拷贝2. 浅拷贝的实现方式3. 浅拷贝的行为演示四、深拷贝Deep Copy1. 什么是深拷贝2. 深拷贝的实现方式3. 深拷贝的行为演示五、深入理解自定义对象的拷贝1. 自定义类与 __copy__、__deepcopy__2. 不可变对象的特殊情况六、浅拷贝与深拷贝的对比七、容易踩的坑八、实际应用场景九、性能考量与最佳实践十、总结在 Python 中进行变量赋值、函数传参、数据结构复制等操作时经常会遇到一个经典问题浅拷贝Shallow Copy与深拷贝Deep Copy的区别。很多初学者甚至一些有经验的开发者在面对嵌套结构时常常因为理解不深而踩坑导致程序出现意想不到的 Bug。一、从一个简单的例子开始先来看一段代码猜猜输出结果是什么python original [1, 2, [3, 4]] copy1 original.copy() # 浅拷贝 copy2 original[:] # 也是浅拷贝 original[2][0] 99 original[0] 100 print(original) # [100, 2, [99, 4]] print(copy1) # ??? print(copy2) # ???如果你以为 copy1 和 copy2 都保持原样 [1, 2, [3, 4]]就犯了经典错误。实际应该是python print(copy1) # [1, 2, [99, 4]] print(copy2) # [1, 2, [99, 4]]为什么 copy1 的第一个元素没变但内部列表却变了这就要从浅拷贝的本质说起。二、Python 中的对象与引用在理解拷贝之前必须先掌握 Python 中变量与对象的关系。Python 中变量名只是对象的一个标签或引用实际的数据存储在内存中的对象里。python a [1, 2, 3] b a # b 和 a 引用同一个对象 b.append(4) print(a) # [1, 2, 3, 4] a 也被修改了这里 b a 只是让 b 指向了 a 所指向的对象并没有创建新的对象。这种赋值称为引用赋值。拷贝则是创建新对象但根据复制深度不同新对象与原对象的关系也不同。三、浅拷贝Shallow Copy1. 什么是浅拷贝浅拷贝会创建一个新的对象但它只复制原对象中元素的引用而不会递归复制元素本身。换句话说新对象中的子对象仍然是原对象中子对象的引用。2. 浅拷贝的实现方式Python 中常见的浅拷贝方式使用 copy 模块的 copy() 函数import copy; new_obj copy.copy(obj)对于列表list.copy() 或 list[:] 或 list(list_obj)对于字典dict.copy() 或 dict(dict_obj)对于集合set.copy()python import copy lst [1, 2, [3, 4]] lst_copy copy.copy(lst) # 浅拷贝 lst_slice lst[:] # 也是浅拷贝 lst_construct list(lst) # 浅拷贝 print(lst_copy is lst) # False外层对象不同 print(lst_copy[2] is lst[2]) # True内部列表是同一个3. 浅拷贝的行为演示python original [1, 2, [3, 4]] shallow original.copy() original[0] 100 # 修改外层不可变元素int original[2][0] 99 # 修改内部可变列表 print(original) # [100, 2, [99, 4]] print(shallow) # [1, 2, [99, 4]]修改 original[0] 时由于整数是不可变对象shallow[0] 仍然指向原来的整数 1所以不受影响。修改 original[2][0] 时因为 original[2] 和 shallow[2] 指向的是同一个列表对象所以修改会反映到 shallow 中。关键点浅拷贝只保证外层容器是新对象但内部的元素依然是原对象的引用。四、深拷贝Deep Copy1. 什么是深拷贝深拷贝会递归地复制原对象中的所有元素创建一个完全独立的新对象。新对象与原对象没有任何共享的引用关系修改原对象不会影响新对象反之亦然。2. 深拷贝的实现方式使用 copy 模块的 deepcopy() 函数import copy; new_obj copy.deepcopy(obj)python import copy lst [1, 2, [3, 4]] deep copy.deepcopy(lst) print(deep is lst) # False print(deep[2] is lst[2]) # False内部列表也被复制了3. 深拷贝的行为演示python import copy original [1, 2, [3, 4]] deep copy.deepcopy(original) original[0] 100 original[2][0] 99 print(original) # [100, 2, [99, 4]] print(deep) # [1, 2, [3, 4]] 完全不受影响深拷贝实现了真正的“备份”无论原对象结构多复杂新对象都拥有自己独立的数据。五、深入理解自定义对象的拷贝1. 自定义类与 __copy__、__deepcopy__对于自定义对象copy.copy() 和 copy.deepcopy() 会调用对象的 __copy__() 和 __deepcopy__() 方法如果定义了。如果没有定义则默认行为是copy.copy() 会创建一个新实例并递归复制所有属性浅拷贝。copy.deepcopy() 会递归复制所有属性深拷贝。但我们可以通过自定义这些方法来控制复制行为。python import copy class MyClass: def __init__(self, data): self.data data def __copy__(self): # 自定义浅拷贝行为 new_instance type(self)(self.data[:]) # 假设 data 是列表 return new_instance def __deepcopy__(self, memo): # 自定义深拷贝行为 new_instance type(self)(copy.deepcopy(self.data, memo)) return new_instance2. 不可变对象的特殊情况对于不可变对象如 int, str, tuple 等浅拷贝和深拷贝没有区别因为它们无法被修改。但注意如果元组中包含可变对象深拷贝仍然会复制那些可变对象。python import copy tup (1, 2, [3, 4]) tup_copy copy.copy(tup) tup_deep copy.deepcopy(tup) print(tup_copy is tup) # True元组本身不可变copy 可能返回原对象 print(tup_deep is tup) # Falsedeepcopy 会递归复制内部列表 print(tup_deep[2] is tup[2]) # False六、浅拷贝与深拷贝的对比特性浅拷贝深拷贝复制层级只复制最外层对象递归复制所有层级内存占用小共享内部对象大完全独立速度快慢与原对象关系内部可变对象会相互影响完全独立互不影响适用场景简单数据结构无需完全隔离复杂嵌套结构需要完全隔离七、容易踩的坑坑1认为 是拷贝python a [1, 2, 3] b a b.append(4) print(a) # [1, 2, 3, 4] a 也被改了很多人会以为 b a 创建了一个副本实际上只是多了一个引用。坑2混淆 copy() 与 deepcopy() 对嵌套结构的影响python original [[1, 2], [3, 4]] shallow original.copy() shallow[0][0] 99 print(original) # [[99, 2], [3, 4]] 原数据被改了期望的是复制却因为浅拷贝共享了内部列表导致原数据被意外修改。坑3深拷贝时遇到循环引用python import copy a [] b [a] a.append(b) # 形成循环引用 c copy.deepcopy(a) # 默认可以处理不会无限递归 print(c) # [[[...]]]deepcopy 内部使用 memo 字典记录已经复制过的对象可以安全处理循环引用。坑4认为所有对象都能被深拷贝有些对象如文件对象、线程锁、数据库连接等不能被复制深拷贝会抛出异常。对于这类对象通常需要自定义 __deepcopy__ 方法返回自身或合适的表示。python import copy f open(test.txt, w) # copy.deepcopy(f) # TypeError: cannot pickle _io.TextIOWrapper object八、实际应用场景场景1防御性编程当需要将对象传递给不可信的函数且不希望函数修改原对象时可以用深拷贝。python def process(data): # 可能会修改 data但不想影响外部 data_local copy.deepcopy(data) # ... 操作 data_local场景2缓存与状态保存在实现撤销、重做功能时需要保存对象的历史状态深拷贝可以保证状态独立。python class Document: def __init__(self): self.content [] def save_state(self): return copy.deepcopy(self.content) def restore_state(self, state): self.content state场景3多线程编程多个线程需要独立处理同一份数据副本时可以使用深拷贝避免竞态条件。场景4配置管理当多个模块需要基于同一份默认配置生成自己的独立配置时深拷贝可以防止互相干扰。九、性能考量与最佳实践1. 避免不必要的深拷贝深拷贝可能非常耗时尤其是对于大型对象。尽量只在必要时使用。python # 如果只需要复制一层用浅拷贝 new_list old_list[:] # 如果内部元素是不可变对象浅拷贝就够了 nums [1, 2, 3] new_nums nums[:] # 安全因为整数不可变2. 自定义复制方法提高效率对于自定义类可以重写 __copy__ 和 __deepcopy__ 来优化复制行为比如共享某些不必复制的部分。3. 利用 copyreg 模块注册复制函数对于无法直接复制的对象如第三方库中的类型可以通过 copyreg 模块注册复制函数。十、总结1. 浅拷贝创建新对象但内部元素是原对象的引用。适合简单结构或不需要完全隔离的场景。2. 深拷贝递归复制所有内容得到完全独立的新对象。适合复杂嵌套结构且需要完全隔离的场景。3. 赋值不是拷贝只是引用绑定。4. 对于不可变对象浅拷贝与深拷贝差别不大但若内部包含可变对象深拷贝仍然会复制。5. 使用 copy 模块时注意性能开销避免滥用深拷贝。

更多文章