Python浅拷贝与深拷贝有什么不同?
时间:2026-09-16 来源:华清远见
前言:一个让你深夜加班的数据Bug
想象这样一个场景:你在处理一个电商订单系统,需要复制一份订单数据进行测试。你写下了这样的代码:
original_order = {
"order_id": "ORD001",
"items": [{"name": "Python书籍", "price": 89.9}, {"name": "机械键盘", "price": 399.0}],
"status": "pending"
}
test_order = original_order # 你以为创建了一份副本
test_order["items"][0]["price"] = 79.9 # 修改测试数据
然后你惊讶地发现,原始订单的商品价格也被改成了79.9元!
这个Bug的背后,正是Python中赋值、浅拷贝与深拷贝的区别在作祟。理解这三者的差异,是写出健壮代码的必修课。
一、赋值:不是拷贝,是贴标签
在Python中,赋值语句并不复制对象,而是创建目标和对象之间的绑定关系。变量本质上只是一个指向内存地址的“标签”。
a = [1, 2, [3, 4]]
b = a # 赋值——只是让b也指向a所指向的对象
print(id(a) == id(b)) # True,两个变量指向同一内存地址
当你修改b时,a也会跟着变化,因为它们根本就是同一个对象。这就像给同一本书贴了两个书签,无论从哪个书签翻开,看到的都是同一本书的内容。
⚠️注意:对于不可变对象(如数字、字符串、元组),赋值后的“修改”实际上是创建了新对象,不会影响原对象。但真正容易出问题的是可变对象(列表、字典、自定义类实例等)。
二、浅拷贝:复制外壳,共享内核
浅拷贝会创建一个新的复合对象,然后将原始对象中找到的对象的引用插入其中。通俗地说:浅拷贝只拷贝第一层。
2.1 实现方式
Python提供了多种浅拷贝方式:
import copy
original = [1, 2, [3, 4]]
# 方式一:copy模块的copy()函数
shallow_1 = copy.copy(original)
# 方式二:列表的copy()方法
shallow_2 = original.copy()
# 方式三:切片操作
shallow_3 = original[:]
# 方式四:类型构造器
shallow_4 = list(original)
2.2 浅拷贝的行为
来看一个具体例子:
import copy
arr = [[1, 2, 3], [4, 5, 6]]
shallow_copied = copy.copy(arr)
# 修改浅拷贝的外层——不影响原对象
shallow_copied.append([7, 8, 9])
print(arr) # [[1, 2, 3], [4, 5, 6]] —— 不变
# 修改浅拷贝的内层嵌套列表——会影响原对象!
shallow_copied[0].append(3.5)
print(arr) # [[1, 2, 3, 3.5], [4, 5, 6]] —— 被修改了!
为什么?因为浅拷贝只复制了外层容器,内层的嵌套列表仍然是共享引用。用一张图来理解:
•原始对象:外层列表 → [内层列表A, 内层列表B]
•浅拷贝后:新外层列表 → [内层列表A, 内层列表B](内层列表A和B还是原来的那两个)
这就是所谓“复制表面,共享内核”。
三、深拷贝:完整的平行宇宙
深拷贝通过copy.deepcopy()实现,它会递归地复制对象及其所有子对象,生成一个完全独立的副本。
import copy
arr = [[1, 2, 3], [4, 5, 6]]
deep_copied = copy.deepcopy(arr)
# 修改深拷贝的任何层级,都不会影响原对象
deep_copied[0].append(3.5)
deep_copied.append([7, 8, 9])
print(arr) # [[1, 2, 3], [4, 5, 6]] —— 纹丝不动!
深拷贝的过程就像用3D打印机完整复制了一栋房子——包括所有的家具和装饰,新房子与原房子在物理上完全隔离。
3.1 深拷贝的注意事项
深拷贝也并非万能,有两个常见问题需要注意:
1.递归对象:如果对象包含对自身的引用,深拷贝可能陷入递归循环。deepcopy()通过维护一个“备忘录”(memo字典)来避免这个问题。
2.不可拷贝的类型:模块、方法、文件对象、套接字、栈帧等类型无法被拷贝。对于这些对象,deepcopy()会直接返回原对象。
四、一张表看懂三种方式的区别

五、完整对比示例
import copy
a = [1, 2, 3, 4, ['a', 'b']]
b = a # 赋值
c = copy.copy(a) # 浅拷贝
d = copy.deepcopy(a) # 深拷贝
# 修改原对象
a.append(5)
a[4].append('c')
print('a =', a) # [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print('b =', b) # [1, 2, 3, 4, ['a', 'b', 'c'], 5] —— 跟着变
print('c =', c) # [1, 2, 3, 4, ['a', 'b', 'c']] —— 外层独立,内层跟着变
print('d =', d) # [1, 2, 3, 4, ['a', 'b']] —— 完全独立!
运行结果清晰地展示了三者的差异:
•b和a完全同步
•c的外层独立但内层共享
•d完全独立
六、特殊情况:不可变对象
对于只包含不可变对象(数字、字符串、元组)的复合对象,浅拷贝和深拷贝的行为差异并不明显:
import copy
a = [1, 2, 3, 4]
b = copy.copy(a)
c = copy.deepcopy(a)
# 对于简单列表,浅拷贝和深拷贝效果类似
a.append(5)
print(b) # [1, 2, 3, 4] —— 不变
print(c) # [1, 2, 3, 4] —— 不变
但一旦涉及嵌套的可变对象,差异就会显现。
七、实战建议:何时用哪种?
浅拷贝适用场景
•对象只包含不可变子对象(如纯数字列表)
•需要共享嵌套子对象(有意为之的设计)
•追求性能和内存效率
深拷贝适用场景
•对象包含多层嵌套的可变结构
•需要完全独立的副本,避免任何联动修改
•多线程环境下传递数据
•接口自动化测试中确保每组测试数据独立
性能考量
浅拷贝只复制顶层,内存开销小、速度快;深拷贝需要递归复制所有层级,内存开销大、速度慢。在性能敏感的场景下,优先考虑浅拷贝能否满足需求。
八、总结
1.赋值:只是给同一个对象贴了新标签,不是拷贝。
2.浅拷贝:创建新容器,但嵌套对象仍是共享引用——“各有一栋楼,共用一间房”。
3.深拷贝:递归复制所有层级,完全独立——“各有一栋楼,各有各的房间”。
记住一句话:浅拷贝管一层,深拷贝管到底。理解了这个核心差异,你就能在开发中从容选择合适的拷贝方式,避免那些令人抓狂的“数据联动Bug”。

