Python中深浅拷贝的区别详细分析
作者:某学酥 Kevin
简而言之:
深浅拷贝的区别关键在于拷贝的对象类型是否可变。
我们可以总结出以下三条规则:
- 对于可变对象来说,深拷贝和浅拷贝都会开辟新地址,完成对象的拷贝
- 而对于不可变对象来说,深浅拷贝都不会开辟新地址,只是建立引用关联
,等价于赋值- 对于复合对象来说,浅拷贝只考虑最外层的类型,复合类型数据中的元
素仍为引用关系;而深拷贝对复合对象会递归应用前两条规则
背后的逻辑也很容易理解,我们可以在 Python 的官方文档里找到如下解释:
Python 的赋值语句不复制对象,而是创建目标和对象的绑定关系。对于自身可变,或包含可变项的集合,有时要生成副本用于改变操作,而不必改变原始对象。
- 不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组);
- 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。
下面我们通过对不同类型的对象进行深浅拷贝来逐条说明上述规则:
不可变对象
以元组(tuple)为例:
import copy tup1 = (991, "abc") tup2 = copy.copy(tup1) # 浅拷贝 # tup2 = tup1 # 在这个例子里浅拷贝等价于赋值 print(id(tup1)) print(id(tup2)) print(tup1 == tup2) print(tup1 is tup2) # 2457279675264 # 2457279675264 # True # True tup2 = copy.deepcopy(tup1) # 深拷贝 print(id(tup1)) print(id(tup2)) print(tup1 == tup2) print(tup1 is tup2) # 1291830377344 # 1291830377344 # True # True
我们可以看到,对于不可变对象,深拷贝还是浅拷贝都不会为我们对象建立真正的副本,tup2 和 tup1的地址完全相同,实际上引用的是同一个对象。
可变对象
以列表(list)为例:
import copy lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))] lis2 = copy.copy(lis1) # 浅拷贝 print(id(lis1)) print(id(lis2)) print(lis1 == lis2) print(lis1 is lis2) # 2491304912896 # 2491304912960 # True # False lis2 = copy.deepcopy(lis1) # 深拷贝 print(id(lis1)) print(id(lis2)) print(lis1 == lis2) print(lis1 is lis2) # 2841088174144 # 2841088174208 # True # False
可以看到,对于可变对象来说,深拷贝和浅拷贝都会开辟新地址,完成对象的拷贝。
复合对象
其实上面例子中的列表同时还是一个复合对象(即包含其他对象的对象)。
对于复合对象来说,浅拷贝只考虑最外层的类型,复合类型数据中的元素仍为引用关系。深拷贝对复合对象会递归应用前两条规则。
import copy tup3 = (991, "abc", []) tup4 = copy.copy(tup3) # 浅拷贝 print(tup3 is tup4) # True print(tup3[-1] is tup4[-1]) # True lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))] lis2 = copy.copy(lis1) # 浅拷贝 print(lis1 is lis2) # False # 虽然 lis1 和 lis2 的地址不同,但其中的每个元素都各自指向同一个对象 print(lis1[0] is lis2[0]) # True print(lis1[1] is lis2[1]) # True print(lis1[2] is lis2[2]) # True print(lis1[3] is lis2[3]) # True print(lis1[4] is lis2[4]) # True print(lis1[5] is lis2[5]) # True print(lis1[6] is lis2[6]) # True print(lis1[7] is lis2[7]) # True
可以看到对于复合对象,其最外层的逻辑和前文提到的相同,即可变对象开辟新地址,不可变对象不开辟新地址。但复合对象内的元素全部只是建立引用关联,地址相同。
而深拷贝还需确认复合对象中的所有元素是否都不可变然后在对元素递归应用前两条规则。只要复合对象本身是可变的或者其中存在可变对象,则都会完成拷贝。
import copy lis1 = [991, "abc", (9, 993), [994, 995], [888,887], {"name": "Tom"}, (996, [997, 998]), (888,(886, 886))] lis2 = copy.deepcopy(lis1) # 深拷贝 print(lis1 is lis2) # False,列表是可变对象,深复制后地址改变 print(lis1[0] is lis2[0]) # True,索引0是整数,不可变,地址不变 print(lis1[3] is lis2[3]) # False, 索引3是列表,可变,地址改变 tup3 = (991, "abc", []) tup4 = copy.deepcopy(tup3) # 深拷贝 print(tup3 is tup4) # False,虽然 tup3 是不可变对象,但其内部存在可变对象,所以深复制后地址仍然改变 print(tup3[0] is tup4[0]) # True,索引 0 是整数,不可变,地址不变 print(tup3[1] is tup4[1]) # True,索引 3 是字符串,不可变,地址不变 print(tup3[2] is tup4[2]) # False,索引 3 是列表,可变,地址改变
参考:
copy — Shallow and deep copy operations — Python 3.10.7 documentation(Python3 文档)
补充:下面解释可变类型和不可变类型的嵌套使用
(1)可变类型:
浅拷贝和深拷贝只要最外层是可变类型都会生成新的对象
- [] 或者{}, 浅拷贝和深拷贝都会生成新的对象
- [[],[]]列表的嵌套,可变类型嵌套了可变类型,浅拷贝:只拷贝最外层,会生成新的对象,内层是引用。深拷贝:外层和内层都会进行拷贝,都是全新的对象,都有独立的存储空间
- [(),()] 外层可变,内层不可变,浅拷贝:只拷贝最外层,会生成新的对象,内层是引用。深拷贝:外层和内层都会进行拷贝,外层会生成新对象,但是由于内层是不可变类型,所以内层依然是引用
- [(),[]] 外层可变,内层有一个是可变,浅拷贝:只拷贝最外层,会生成新的对象,内层是引用。深拷贝:外层和内层都会进行拷贝,外层会生成新对象,内层可变对象会生成新对象,内层不可变对象是引用
(2)不可变类型:
最外层是不可变类型,浅拷贝就一定是引用
- Number、字符串或者(), 浅拷贝和深拷贝都是引用
- ([],[]), copy浅拷贝:只会拷贝最外层,内层只是引用,但是最外层是不可变,拷贝之后毫无意义,仅仅是引用关系。deepcopy:从外层到内层都会拷贝,内层是可变,为了达到和原来的数据完全隔离,会生成全新的对象
- ((),()) 完全不可变,拷贝了之后如果生成新的数据也无法修改,所以不管深拷贝还是浅拷贝都是引用
- ((),[]) 外层不可变,但是内层有一个是可变,copy依然是引用,deepcopy,会生成新的对象,内层的不可变类型是引用,可变类型会生成新的对象。
总结
到此这篇关于Python中深浅拷贝的区别的文章就介绍到这了,更多相关Python深浅拷贝的区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!