Python数据拷贝机制:=、copy()与deepcopy()的深度辨析
a name="4-总结与选择"></a>赋值 (: 当你只是想用不同的名字引用内存中的同一个对象时使用。这是引用,不是拷贝。浅拷贝 ([:].copy()方法)当你想要一个新的顶层容器,但可以接受内部可变元素被共享时使用。或者当你知道容器内部元素都是不可变类型时(此时浅拷贝效果等同深拷贝,且更快)。适用于只修改顶层结构而不希望影响原对象,但要特别注意共享子对象的修改会相互影响。深拷贝 (当你需要
在Python中处理数据,尤其是可变对象(如列表、字典)时,理解赋值、浅拷贝和深拷贝之间的区别至关重要。错误地使用拷贝机制可能导致数据意外共享和修改,引发难以察觉的bug。本篇将深入辨析这三者,并探讨何时应使用Python的 copy
模块。
一、赋值操作:仅仅是“贴标签”
<a name="1-赋值操作-只是贴标签不是拷贝"></a>
当你执行 b = a 这样的赋值语句时,其行为取决于 a 指向的对象类型:
- 如果
a
指向的是不可变对象 (如数字、字符串、元组),b
会得到这个值。由于不可变对象的值不能改变,通常不会因共享而产生问题。 - 如果
a
指向的是可变对象 (如列表、字典、集合),b = a
并不会创建a
的一个新副本。它只是创建了一个新的名称(标签)b
,这个新标签与a
一样,都指向内存中同一个对象。
通俗理解: “你给同一个钱包(内存中的对象)贴上了两个名字标签(a
和 b
)。通过任何一个名字标签往钱包里放钱或拿钱,钱包里的钱都会变,另一个名字标签看到的也是这个变化后的钱包。”
a = [1, 2, [30, 40]] # a 是一个包含列表的列表 (可变对象)
b = a # b 和 a 指向同一个列表对象
print(f"a: {a}, id(a): {id(a)}")
print(f"b: {b}, id(b): {id(b)}")
print(f"a is b: {a is b}") # 输出: True (它们是同一个对象,id相同)
b.append(5) # 通过 b 修改列表
print(f"a after b.append(5): {a}") # 输出: [1, 2, [30, 40], 5] (a也跟着变了)
b[2][0] = 300 # 通过 b 修改嵌套列表的内容
print(f"a after b[2][0]=300: {a}") # 输出: [1, 2, [300, 40], 5] (a的嵌套列表内容也变了)
从上面代码可见,修改 b
会直接影响 a
,因为它们指向的是同一个内存地址。
二、浅拷贝 (Shallow Copy):复制顶层,共享内层
<a name="2-浅拷贝-shallow-copy"></a>
2.1 浅拷贝的行为
通俗理解:“就像复印一本书的第一层目录。书的封面和目录本身(顶层对象)是新复印出来的,所以你有了一本新的书(新的顶层容器对象)。但是,如果目录里的条目(元素)指向的是其他共享的书籍(内部的可变对象),那么复印出来的目录条目仍然指向那些原来的、共享的书籍的‘地址’(引用),而不是把那些共享的书籍也重新复印一遍。”
技术上讲,浅拷贝会创建一个新的顶层容器对象。对于容器内部的元素:
- 如果是基本数据类型(如数字、字符串,它们是不可变的),则复制其值。
- 如果元素是对其他可变对象的引用(比如列表中的另一个列表,或字典中的一个列表值),则只复制这个“门牌号”(引用),而不是那个门牌号指向的“房子”(实际对象)。
2.2 如何进行浅拷贝
- 使用
copy
模块的copy()
函数:import copy; shallow_copy = copy.copy(original_object)
- 对于列表,可以使用切片操作:
shallow_copy_list = original_list[:]
- 对于某些内置容器类型 (如
list
,dict
,set
),它们有自己的.copy()
方法:shallow_copy_dict = original_dict.copy()
2.3 浅拷贝效果示例
import copy
original_list = [1, 2, [30, 40]] # 内部元素 [30, 40] 是一个可变对象
shallow_copied_list = copy.copy(original_list)
# shallow_copied_list = original_list[:] # 对于列表,效果相同
# shallow_copied_list = original_list.copy() # 对于列表,效果相同
print(f"original_list: {original_list}, id: {id(original_list)}")
print(f"shallow_copied_list: {shallow_copied_list}, id: {id(shallow_copied_list)}")
print(f"original_list is shallow_copied_list: {original_list is shallow_copied_list}") # False (顶层对象不同)
print(f"original_list[2] is shallow_copied_list[2]: {original_list[2] is shallow_copied_list[2]}") # True (内部嵌套的列表是同一个对象!)
# 修改浅拷贝对象的顶层结构
shallow_copied_list.append(5) # 在浅拷贝列表末尾添加元素
print(f"Original list after shallow_copy.append(5): {original_list}") # [1, 2, [30, 40]] (原列表顶层不变)
print(f"Shallow copied list: {shallow_copied_list}") # [1, 2, [30, 40], 5]
# 修改浅拷贝对象内部共享的可变子对象的内容
shallow_copied_list[2][0] = 300 # 修改浅拷贝列表中那个被共享的嵌套列表的元素
print(f"Original list after shallow_copy[2][0]=300: {original_list}") # [1, 2, [300, 40]] (原列表也受影响了!)
print(f"Shallow copied list: {shallow_copied_list}") # [1, 2, [300, 40], 5]
可以看到,修改浅拷贝对象的顶层(如 append(5)
)不影响原对象。但如果修改了浅拷贝对象内部共享的可变子对象(如嵌套列表 [300, 40]
),原对象中对应的共享子对象也会随之改变。
三、深拷贝 (Deep Copy):彻底的“克隆”
<a name="3-深拷贝-deep-copy"></a>
3.1 深拷贝的行为
通俗理解:“这次是彻底的‘克隆’,不仅书本身是全新的,书里面引用的所有其他书籍(内部可变对象),以及那些书籍里面再引用的书籍……所有的一切都会被递归地重新复制一份全新的。新书和旧书,以及它们各自包含的所有层级的内容,都完全独立,互不相干。”
技术上讲,深拷贝会创建一个完全独立的新对象。原对象中包含的所有子对象(以及子对象的子对象,等等)都会被递归地复制。修改深拷贝对象或其任何部分的内部内容,都不会影响到原对象。
3.2 如何进行深拷贝
必须使用 copy 模块的 deepcopy() 函数:
import copy; deep_copy = copy.deepcopy(original_object)
3.3 深拷贝效果示例
import copy
original_list = [1, 2, [30, 40]]
deep_copied_list = copy.deepcopy(original_list)
print(f"original_list: {original_list}, id: {id(original_list)}")
print(f"deep_copied_list: {deep_copied_list}, id: {id(deep_copied_list)}")
print(f"original_list is deep_copied_list: {original_list is deep_copied_list}") # False (顶层对象不同)
print(f"original_list[2] is deep_copied_list[2]: {original_list[2] is deep_copied_list[2]}") # False (内部嵌套的列表也是新对象了!)
# 修改深拷贝对象的顶层
deep_copied_list.append(5)
print(f"Original list after deep_copy.append(5): {original_list}") # [1, 2, [30, 40]] (原列表顶层不变)
print(f"Deep copied list: {deep_copied_list}") # [1, 2, [30, 40], 5]
# 修改深拷贝对象中内部列表的元素
deep_copied_list[2][0] = 300
print(f"Original list after deep_copy[2][0]=300: {original_list}") # [1, 2, [30, 40]] (原列表完全不受影响!)
print(f"Deep copied list: {deep_copied_list}") # [1, 2, [300, 40], 5]
注意: 深拷贝可能会比浅拷贝慢,因为它需要递归复制所有对象。如果对象图中存在循环引用,copy.deepcopy()
也能正确处理(它会记录已拷贝的对象以避免无限递归)。
四、总结与选择:何时使用何种拷贝?
<a name="4-总结与选择"></a>
- 赋值 (
=
): 当你只是想用不同的名字引用内存中的同一个对象时使用。这是引用,不是拷贝。 - 浅拷贝 (
copy.copy()
,[:]
,.copy()
方法):- 当你想要一个新的顶层容器,但可以接受内部可变元素被共享时使用。
- 或者当你知道容器内部元素都是不可变类型时(此时浅拷贝效果等同深拷贝,且更快)。
- 适用于只修改顶层结构而不希望影响原对象,但要特别注意共享子对象的修改会相互影响。
- 深拷贝 (
copy.deepcopy()
):- 当你需要一个对象的完全独立副本,确保对副本的任何修改(包括深层嵌套对象的修改)都不会影响原对象时使用。
- 这是最安全的复制方式,尤其在处理复杂嵌套的可变数据结构时,但可能代价较高(时间和内存)。
五、与Java拷贝机制的对比
<a name="5-vs-java"></a>
- 对象赋值: Java中的对象赋值 (
Object b = a;
) 也是引用赋值,行为与Python的=
对可变对象的赋值类似,a
和b
指向同一块内存。 - 浅拷贝: Java中,
Object
类的clone()
方法通常设计为实现浅拷贝(但具体行为取决于类的实现,需要类实现Cloneable
接口并重写clone()
方法)。例如,ArrayList
的clone()
是浅拷贝其元素引用。 - 深拷贝: Java中实现深拷贝通常没有像Python
copy.deepcopy()
这样直接的内置通用函数。一般需要开发者手动编码实现,例如:- 递归地为所有成员对象创建新的副本。
- 利用对象的序列化和反序列化机制(将对象写入字节流再从字节流中读出,可以得到一个全新的对象图,但这要求对象及其所有成员都实现
Serializable
接口)。

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)