发布于2026-07-06 阅读(0)
扫一扫,手机访问
在 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]]
为什么第一个元素没变,内部的列表却跟着改了?答案就藏在“浅拷贝”的本质里。
在讨论拷贝之前,得先弄清楚 Python 里变量和对象的关系。一句话概括:变量名只是个标签,真正的数据住在内存里的对象上。
python a = [1, 2, 3] b = a # b 和 a 引用同一个对象 b.append(4) print(a) # [1, 2, 3, 4] a 也被改了
b = a 并没有创造新列表,只是让 b 指向 a 指向的那个对象。这种操作叫引用赋值。
拷贝则不同——它会新建一个对象,但新对象和原对象的关系取决于复制深度。
浅拷贝会生成一个新容器,但容器里的每个元素仍然是对原对象中对应元素的引用。用大白话说:外壳换了个新的,但里面的东西还是原来的那批。
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,内部列表是同一个
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 身上。
总结一句话:浅拷贝只管外层是新对象,内部的元素全是原对象的引用。
深拷贝会把原对象里的所有元素递归地复制一遍,最终得到一个彻彻底底独立的新对象。原对象和新对象之间没有任何共享的引用,你改你的,我改我的,互不干扰。
Python 只提供了 copy.deepcopy() 这一种标准做法:
python import copy lst = [1, 2, [3, 4]] deep = copy.deepcopy(lst) print(deep is lst) # False print(deep[2] is lst[2]) # False,内部列表也被复制了
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]] 完全不受影响
深拷贝实现了真正的“备份”——无论结构多复杂,新的就是新的,旧的还是旧的。
对于自定义的类,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_instance
对于 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) # False,deepcopy 会递归复制内部列表 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 也被改了
老生常谈但反复有人犯错——= 只是多贴了一张标签,不是复制数据。
坑2:误以为 copy() 能保护所有层级
python original = [[1, 2], [3, 4]] shallow = original.copy() shallow[0][0] = 99 print(original) # [[99, 2], [3, 4]] 原数据被改了!
明明用了 copy(),原数据却还是被污染了。原因就是浅拷贝只复制了外层列表,内部子列表依然是引用。
坑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 sa ve_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 模块时,记住性能开销——深拷贝花了更多时间和内存,别滥用。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8