Python之深浅拷贝
学习深浅拷贝前,我们先了解一下Python中6大标准数据类型,其中有3个不可变数据类型,3个可变数据类型。标准数据类型详解
-
不可变数据类型:
- Number(整型)
- String (字符串)
- Tuple (元组)
-
可变数据类型:
- List(列表)
- Dictionary(字典)
- Set(集合)
深拷贝和浅拷贝总结:
浅拷贝:(copy)
- 对于不可变类型 Number 、String 、Tuple,浅拷贝仅仅是地址的指向,并不会开辟新的地址空间。
- 对于可变数据类型 List 、Dictionary 、Set,浅拷贝会开辟新的地址空间(仅仅是最顶层开辟新的地址空间,里面的元素地址还是一样的),进行浅拷贝。
- 浅拷贝后,改变原始对象中可变数据类型里面元素的值,会同时影响拷贝对象的元素的值;改变原始对象中不可变数据类型里面元素的值,只有原始对象里面的值会改变。(操作拷贝对象对原始对象的也是同理)
深拷贝(deepcopy)
- 在深拷贝中,对于不可变数据类型 Number 、String 、Tuple,深拷贝仍然是地址的指向,并不会开辟新的地址空间。
- 对于可变数据类型 List 、Dictionary 、Set,深拷贝会开辟新的地址空间(最顶层地址和里面的元素地址都会开辟新的地址空间),进行深拷贝。
- 深拷贝后,改变原始对象中的值(不区分可变类型和不可变类型),只有原始对象中的值受影响,拷贝对象不会被影响;同理,改变拷贝对象中的值(不区分可变类型和不可变类型),只有拷贝对象中的值受影响,原始对象不会被影响。
由于下面的案例比较长,主要是为了验证上面的总结,个人建议,结合着上面的总结,把下面的案例自己动手操作一遍,你可以深浅拷贝有更加深刻的了解。
浅拷贝案例
import copy
# 不可变数据类型 Number String Tuple
# 对于不可变数据类型,浅拷贝仅仅是地址的指向,不会开辟新的地址空间
# 对于不可变类型 Number 操作
num1 = 17
num2 = copy.copy(num1)
print("num1: " + str(num1) + ' , id :' + str(id(num1))) # num1: 17 , id :1346137664
print("num2: " + str(num2) + ' , id :' + str(id(num2))) # num1: 17 , id :1346137664
# num1 和 num2 的地址都相同
# 改变原始数据num1 只有 num1 受影响,拷贝对象不受影响
num1 = 55
print("num1: " + str(num1) + ' , id :' + str(id(num1))) # num1: 55 , id :1346138880
print("num2: " + str(num2) + ' , id :' + str(id(num2))) # num2: 17 , id :1346137664
# 对于不可变类型 String 操作 start
str1 = "hello"
str2 = copy.copy(str1)
print("str1: " + str(id(str1))) # str1: 2253301326824
print("str2: " + str(id(str2))) # str2: 2253301326824
# str1 和 str2 的地址都相同
# 对于不可变类型 Tuple 操作 start
tup1 = (18,'tom')
tup2 = copy.copy(tup1)
print("tup1: " + str(id(tup1))) # tup1: 2587894715912
print("tup2: " + str(id(tup2))) # tup2: 2587894715912
# tup1 和 tup2 的地址都相同
# 可变数据类型 List、Dictionary、Set
# 对于可变数据类型,浅拷贝会开辟新的空间地址(仅仅是最顶层开辟了新的空间)
# 对于可变类型 List 的操作 start
list1 = [33,44]
list2 = copy.copy(list1) # list2列表 浅拷贝 list1列表
print("list1中是: " + str(list1) + " , 地址是" + str(id(list1)))
print("list2中是: " + str(list1) + " , 地址是" + str(id(list2)))
# list1中是: [33, 44] , 地址是2301868933192
# list2中是: [33, 44] , 地址是2301868661192
# list1 和 list2 的地址不相同
print("list1[0]中字符串是: "+ str(list1[0]) + " , 地址是" + str(id(list1[0])))
print("list2[0]中字符串是: "+ str(list2[0]) + " , 地址是" + str(id(list2[0])))
# list1[0]中字符串是: 33 , 地址是1346138176
# list2[0]中字符串是: 33 , 地址是1346138176
# list1[0] 和 list2[0] 的地址相同
# 对于可变类型 Dictionary 的操作 start
dic1 = {"one":"33","two":"44"}
dic2 = copy.copy(dic1)
print("dic1中内容是: " + str(dic1) + " , 地址是: " + str(id(dic1)))
print("dic2中内容是: " + str(dic2) + " , 地址是: " + str(id(dic2)))
# dic1中内容是: {'one': '33', 'two': '44'} , 地址是: 2332667211712
# dic2中内容是: {'one': '33', 'two': '44'} , 地址是: 2332667215944
# dic1 和 dic2 的地址不同
print('dic1["one"]中内容是: ' + str(dic1["one"]) + " , 地址是: " + str(id(dic1["one"])))
print('dic2["one"]中内容是: ' + str(dic2["one"]) + " , 地址是: " + str(id(dic2["one"])))
# dic1["one"]中内容是: 33 , 地址是: 2747176712768
# dic2["one"]中内容是: 33 , 地址是: 2747176712768
# dic1["one"] 和 dic2["one"]的地址相同
# 对于可变类型 Set 的操作 start
set1 = {"ww","11"}
set2 = copy.copy(set1)
print("set1中内容是: " + str(set1) + " , 地址是: " + str(id(set1)))
print("set2中内容是: " + str(set2) + " , 地址是: " + str(id(set2)))
# set1中内容是: {'ww', '11'} , 地址是: 1787411341832
# set2中内容是: {'ww', '11'} , 地址是: 1787411342504
# set1 和 set2 的地址不同
对 可变数据类型 进行浅拷贝的进阶篇
由于原理都是一样的,下面的案例便以列表为例
# 对 List(列表) 进行浅拷贝的进阶篇 : 当修改列表中 '可变类型' 和 '不可变类型’ 的值后的影响
# 浅拷贝,创建出一个新对象,并把旧对象中的元素的 引用地址 拷贝到新对象中的元素当中;
# 简单的说,就是两个 对象中的元素 通过浅拷贝指向的还是同一个地址
import copy
list1 = [1,2]
list2 = [3,4]
num = 55
list_sum1 = [list1,list2,num] # 把 list1 、list2 、num 合并到列表 list_sum1中
list_sum2 = copy.copy(list_sum1) # 把 列表list_sum1 中的元素浅拷贝到 列表list_sum2 中
print(list_sum1) # list_sum1 中的值:[[1, 2],[3, 4],55]
print(list_sum2) # list_sum2 中的值:[[1, 2],[3, 4],55]
print("num上: " + str(num) + " , num上的id: "+ str(id(num)))
num = 88
print("num下: "+ str(num) +" , num下的id: "+ str(id(num)))
# num上: 55 , num上的id: 1346138880
# num下: 88 , num下的id: 1346139936
# 上面的num 的地址 和 下面的num 的地址不一样
print("把 88 复制给 num 后 , list_sum1中元素值: " + str(list_sum1))
print("把 88 复制给 num 后 , list_sum2中元素值: " + str(list_sum2))
# 把 88 复制给 num 后 , list_sum1中元素值: [[1, 2], [3, 4], 55]
# 把 88 复制给 num 后 , list_sum2中元素值: [[1, 2], [3, 4], 55]
# 把 88 复制给num 会得到一个全新的地址,这时 list_sum1 和 list_sum2 中的值不会发生改变
# 因为 这个num 和 上面其他的num 已经没有关系了
list1[0] = 10
# 修改列表 list1 索引为0 的值,会使 list_sum1 和 list_sum2 中 索引为0 的元素的值都会发生改变,
# 因为列表 list1 是可变对象。
print("修改 list1[0] 的值等于 10 后 , list_sum1中元素值: " + str(list_sum1))
print("修改 list1[0] 的值等于 10 后 , list_sum2中元素值: " + str(list_sum2))
# 修改 list1[0] 的值等于 10 后 , list_sum1中元素值: [[10, 2], [3, 4], 55]
# 修改 list1[0] 的值等于 10 后 , list_sum2中元素值: [[10, 2], [3, 4], 55]
list_sum1[0] = 12
print("修改 list1[0] 的值等于 10 后 , list_sum1中元素值: " + str(list_sum1))
print("修改 list1[0] 的值等于 10 后 , list_sum1中元素值: " + str(list_sum2))
修改 list1[0] 的值等于 10 后 , list_sum1中元素值: [12, [3, 4], 55]
修改 list1[0] 的值等于 10 后 , list_sum1中元素值: [[10, 2], [3, 4], 55]
import copy
list1 = [1,2]
list2 = [3,4]
num = 55
list_sum1 = [list1,list2,num] # 把 list1 、list2 、num 合并到列表 list_sum1中
list_sum2 = copy.copy(list_sum1) # 把 列表list_sum1 中的元素浅拷贝到 列表list_sum2 中
print("list_sum1 的值: " + str(list_sum1) + " , list_sum1的id: " + str(id(list_sum1)))
print("list_sum2 的值: " + str(list_sum2) + " , list_sum2的id: " + str(id(list_sum2)))
# list_sum1 的值: [[1, 2], [3, 4], 55] , list_sum1的id: 2750138189000
# list_sum2 的值: [[1, 2], [3, 4], 55] , list_sum2的id: 2750138188936
print("list_sum1[0] 的值: " + str(list_sum1[0]) + " , list_sum1[0]的id: " + str(id(list_sum1[0])))
print("list_sum2[0] 的值: " + str(list_sum2[0]) + " , list_sum2[0]的id: " + str(id(list_sum2[0])))
# list_sum1[0] 的值: [1, 2] , list_sum1[0]的id: 2028849168584
# list_sum2[0] 的值: [1, 2] , list_sum2[0]的id: 2028849168584
print("list_sum1[0][0] 的值: " + str(list_sum1[0][0]) + " , list_sum1[0][0]的id: " + str(id(list_sum1[0][0])))
print("list_sum2[0][0] 的值: " + str(list_sum2[0][0]) + " , list_sum2[0][0]的id: " + str(id(list_sum2[0][0])))
# list_sum1[0][0] 的值: 1 , list_sum1[0][0]的id: 1346137152
# list_sum2[0][0] 的值: 1 , list_sum2[0][0]的id: 1346137152
print("list_sum1[1] 的值: " + str(list_sum1[1]) + " , list_sum1[1]的id: " + str(id(list_sum1[1])))
print("list_sum2[1] 的值: " + str(list_sum2[1]) + " , list_sum2[1]的id: " + str(id(list_sum2[1])))
# list_sum1[1] 的值: [3, 4] , list_sum1[1]的id: 2589457930824
# list_sum2[1] 的值: [3, 4] , list_sum2[1]的id: 2589457930824
# 由上述例子可以看出:
# 改动 list_sum1 中的 可变类型 , 会影响 list_sum2 ; 改动 list_sum1中的可变类型,同理会影响 list_sum1
# 改动 list_sum2 中的 不可变类型 , 只影响 lsit_sum2 自己,不会影响 list_sum1
关于 = 符号,可以参考python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域
深拷贝案例
由于篇幅有限,深拷贝的案例就不在列举了,有兴趣的可以自己按照浅拷贝的案例方式自己动手实践一下,后面字典自带的拷贝可以实现深拷贝,可以结合下面的案例。
- 浅拷贝,除了父层拷贝,对子元素也要进行拷贝(本质上递归浅拷贝)
- 经过深拷贝后,原对象和拷贝的新对象所有的元素地址都是不一样的
- 可以用分片表达式进行深拷贝
- 字典自带的copy方法可以实现深拷贝
# 在深拷贝中,对于不可变类型 Number String Tuple,深拷贝仍然是地址指向,不会开辟新空间拷贝值
# 对于可变类型 List Dictionaty Set ,深拷贝会开辟新的空间地址,进行拷贝
# 浅拷贝,除了顶层拷贝,还对子元素进行了拷贝(本质上递归浅拷贝)
# 经过深拷贝后,原始对象和拷贝对象所有的元素地址都没有相同的了
# 在之前的浅拷贝中,子元素是不会开辟新空间来拷贝的
# 但是在深拷贝中,子元素也进行拷贝
其他方式的拷贝
分片式拷贝
# 分片表达式拷贝 start
list1 = [1,2]
list2 = [3,4]
num = 55
list_sum = [list1,list2,num]
print(list_sum)
# [[1, 2], [3, 4], 55]
nlist = list_sum[:]
print(nlist)
# [[1, 2], [3, 4], 55]
print('list_sum 的值: ' + str(list_sum) + ', list_sum 的id: ' + str(id(list_sum)))
# list_sum 的值: [[1, 2], [3, 4], 55], list_sum 的id: 2042818430088
print('list_sum[0] 的值: ' + str(list_sum[0]) + ', list_sum[0] 的id: ' + str(id(list_sum[0])))
# list_sum[0] 的值: [1, 2], list_sum[0] 的id: 2042818429064
print('list_sum[1][0] 的值: ' + str(list_sum[1][0]) + ', list_sum[1][0] 的id: ' + str(id(list_sum[1][0])))
# list_sum[1][0] 的值: 3, list_sum[1][0] 的id: 1346137216
print('list_sum[2] 的值: ' + str(list_sum[2]) + ', list_sum[2] 的id: ' + str(id(list_sum[2])))
# list_sum[2] 的值: 55, list_sum[2] 的id: 1346138880
print('nlist 的值: ' + str(nlist) + ', nlist 的id: ' + str(id(nlist)))
# nlist 的值: [[1, 2], [3, 4], 55], nlist 的id: 2042818430024
print('nlist[0] 的值: ' + str(nlist[0]) + ', nlist[0] 的id: ' + str(id(nlist[0])))
# nlist[0] 的值: [1, 2], nlist[0] 的id: 2042818429064
print('nlist[1][0] 的值: ' + str(nlist[1][0]) + ', nlist[1][0] 的id: ' + str(id(nlist[1][0])))
# nlist[1][0] 的值: 3, nlist[1][0] 的id: 1346137216
print('nlist[2] 的值: ' + str(nlist[2]) + ', nlist[2] 的id: ' + str(id(nlist[2])))
# nlist[2] 的值: 55, nlist[2] 的id: 1346138880
# 分片式拷贝是浅拷贝
字典自带的拷贝
**字典自带的 copy 方法可以实现深拷贝 **
# 字典自带的 copy 方法可以实现的拷贝 start
import copy
dic0 = {'key1':[0,1,2],'key2':10}
dic1 = dic0 # 赋值
dic2 = dic0.copy() # 浅拷贝
dic3 = copy.deepcopy(dic0) # 深拷贝
print("原本值dic0的值: " + str(dic0) + ", 原本值dic0的id:" + str(id(dic0)))
# 原本值dic0的值: {'key1': [0, 1, 2], 'key2': 10}, 原本值dic0的id:2541879095232
print("赋值 dic1 的值: " + str(dic1) + ", 赋值 dic1 的id:" + str(id(dic1)))
# 赋值 dic1 的值: {'key1': [0, 1, 2], 'key2': 10}, 赋值 dic1 的id:2541879095232
print("浅拷贝dic2的值: " + str(dic2) + ", 浅拷贝dic2的id:" + str(id(dic2)))
# 浅拷贝dic2的值: {'key1': [0, 1, 2], 'key2': 10}, 浅拷贝dic2的id:2541879099464
print("深拷贝dic3的值: " + str(dic3) + ", 深拷贝dic2的id:" + str(id(dic3)))
# 深拷贝dic3的值: {'key1': [0, 1, 2], 'key2': 10}, 深拷贝dic2的id:2541879972488
#对于字典中key(键)的值是可变类型的情况
print("原本值dic0['key1']的值: " + str(dic0['key1']) + ", 原本值dic0['key1'] 的id:" + str(id(dic0['key1'])))
# 原本值dic0['key1']的值: [0, 1, 2], 原本值dic0['key1'] 的id:2541881647240
print("赋值 dic1['key1'] 的值: " + str(dic1['key1']) + ", 赋值 dic1['key1'] 的id:" + str(id(dic1['key1'])))
# 赋值 dic1['key1'] 的值: [0, 1, 2], 赋值 dic1['key1'] 的id:2541881647240
print("浅拷贝dic2['key1']的值: " + str(dic2['key1']) + ", 浅拷贝dic2['key1'] 的id:" + str(id(dic2['key1'])))
# 浅拷贝dic2['key1']的值: [0, 1, 2], 浅拷贝dic2['key1'] 的id:2541881647240
print("深拷贝dic3['key1']的值: " + str(dic3['key1']) + ", 深拷贝dic3['key1'] 的id:" + str(id(dic3['key1'])))
# 深拷贝dic3['key1']的值: [0, 1, 2], 深拷贝dic3['key1'] 的id:2541881346568
# 对于字典中key(键)的值是不可变类型的情况
print("原本值dic0['key2']的值: " + str(dic0['key2']) + ", 原本值dic0['key2'] 的id:" + str(id(dic0['key2'])))
# 原本值dic0['key2']的值: 10, 原本值dic0['key2'] 的id:1346137440
print("赋值 dic1['key2']的值: " + str(dic1['key2']) + ", 赋值 dic1['key2'] 的id:" + str(id(dic1['key2'])))
# 赋值 dic1['key2']的值: 10, 赋值 dic1['key2'] 的id:1346137440
print("浅拷贝dic2['key2']的值: " + str(dic2['key2']) + ", 浅拷贝dic2['key2'] 的id:" + str(id(dic2['key2'])))
# 浅拷贝dic2['key2']的值: 10, 浅拷贝dic2['key2'] 的id:1346137440
print("深拷贝dic3['key2']的值: " + str(dic3['key2']) + ", 深拷贝dic3['key2'] 的id:" + str(id(dic3['key2'])))
# 深拷贝dic3['key2']的值: 10, 深拷贝dic3['key2'] 的id:1346137440
# 修改字典
dic0["key1"].remove(1)
dic0["key2"] = "33"
print("修改键值后,dic0 的值: " + str(dic0) + ", 修改键值后 dic0 的id:" + str(id(dic0)))
# 修改键值后,dic0 的值: {'key1': [0, 2], 'key2': '33'}, 修改键值后 dic0 的id:2541879095232
print("修改键值后,赋值 dic1 的值: " + str(dic1) + ",修改键值后 赋值 dic1 的id:" + str(id(dic1)))
# 修改键值后,赋值 dic1 的值: {'key1': [0, 2], 'key2': '33'},修改键值后 赋值 dic1 的id:2541879095232
print("修改键值后,浅拷贝 dic2 的值: " + str(dic2) + ", 修改键值后,浅拷贝 dic2 的id:" + str(id(dic2)))
# 修改键值后,浅拷贝 dic2 的值: {'key1': [0, 2], 'key2': 10}, 修改键值后,浅拷贝 dic2 的id:2541879099464
print("修改键值后,深拷贝 dic3 的值: " + str(dic3) + ", 修改键值后,深拷贝 dic2 的id:" + str(id(dic3)))
# 修改键值后,深拷贝 dic3 的值: {'key1': [0, 1, 2], 'key2': 10}, 修改键值后,深拷贝 dic2 的id:2541879972488
# 对于修改字典中(key(键)的值是可变类型)的情况
print("修改键值后,dic0['key1'] 的值: " + str(dic0['key1']) + ", 修改键值后 dic0['key1'] 的id:" + str(id(dic0['key1'])))
# 修改键值后,dic0['key1'] 的值: [0, 2], 修改键值后 dic0['key1'] 的id:2541881647240
print("修改键值后,赋值 dic1['key1'] 的值: " + str(dic1['key1']) + ", 修改键值后,赋值 dic1['key1'] 的id:" + str(id(dic1['key1'])))
# 修改键值后,赋值 dic1['key1'] 的值: [0, 2], 修改键值后,赋值 dic1['key1'] 的id:2541881647240
print("修改键值后,浅拷贝 dic2['key1'] 的值: " + str(dic2['key1']) + ", 修改键值后,浅拷贝 dic2['key1'] 的id:" + str(id(dic2['key1'])))
# 修改键值后,浅拷贝 dic2['key1'] 的值: [0, 2], 修改键值后,浅拷贝 dic2['key1'] 的id:2541881647240
print("修改键值后,深拷贝 dic3['key1'] 的值: " + str(dic3['key1']) + ", 修改键值后,浅拷贝 dic3['key1'] 的id:" + str(id(dic3['key1'])))
# 修改键值后,深拷贝 dic3['key1'] 的值: [0, 1, 2], 修改键值后,浅拷贝 dic3['key1'] 的id:2541881346568
# 对于修改字典中 (key(键)的值是不可变类型)的情况
print("修改键值后,dic0['key2'] 的值: " + str(dic0['key2']) + ", 修改键值后dic0['key2'] 的id:" + str(id(dic0['key2'])))
# 修改键值后,dic0['key2'] 的值: 33, 修改键值后dic0['key2'] 的id:2541880133760
print("修改键值后,赋值 dic1['key2'] 的值: " + str(dic1['key2']) + ", 修改键值后, 赋值 dic1['key2'] 的id:" + str(id(dic1['key2'])))
# 修改键值后,dic0['key2'] 的值: 33, 修改键值后dic0['key2'] 的id:2541880133760
print("修改键值后,浅拷贝 dic2['key2'] 的值: " + str(dic2['key2']) + ", 修改键值后,浅拷贝 dic2['key2'] 的id:" + str(id(dic2['key2'])))
# 修改键值后,浅拷贝 dic2['key2'] 的值: 10, 修改键值后,浅拷贝 dic2['key2'] 的id:1346137440
print("修改键值后,深拷贝 dic3['key2'] 的值: " + str(dic3['key2']) + ", 修改键值后,深拷贝 dic3['key2'] 的id:" + str(id(dic3['key2'])))
# 修改键值后,深拷贝 dic3['key2'] 的值: 10, 修改键值后,深拷贝 dic3['key2'] 的id:1346137440
# 综上所述:字典自带的 copy 方法可以实现深拷贝
你可能感兴趣的文章