• Python之深浅拷贝


    Python之深浅拷贝


    学习深浅拷贝前,我们先了解一下Python中6大标准数据类型,其中有3个不可变数据类型,3个可变数据类型。标准数据类型详解

    • 不可变数据类型:

      • Number(整型)
      • String (字符串)
      • Tuple (元组)
    • 可变数据类型:

      • List(列表)
      • Dictionary(字典)
      • Set(集合)

    深拷贝和浅拷贝总结:

    浅拷贝:(copy)

    1. 对于不可变类型 Number 、String 、Tuple,浅拷贝仅仅是地址的指向,并不会开辟新的地址空间。
    2. 对于可变数据类型 List 、Dictionary 、Set,浅拷贝会开辟新的地址空间(仅仅是最顶层开辟新的地址空间,里面的元素地址还是一样的),进行浅拷贝。
    3. 浅拷贝后,改变原始对象中可变数据类型里面元素的值,会同时影响拷贝对象的元素的值;改变原始对象中不可变数据类型里面元素的值,只有原始对象里面的值会改变。(操作拷贝对象对原始对象的也是同理)

    深拷贝(deepcopy)

    1. 在深拷贝中,对于不可变数据类型 Number 、String 、Tuple,深拷贝仍然是地址的指向,并不会开辟新的地址空间。
    2. 对于可变数据类型 List 、Dictionary 、Set,深拷贝会开辟新的地址空间(最顶层地址和里面的元素地址都会开辟新的地址空间),进行深拷贝。
    3. 深拷贝后,改变原始对象中的值(不区分可变类型和不可变类型),只有原始对象中的值受影响,拷贝对象不会被影响;同理,改变拷贝对象中的值(不区分可变类型和不可变类型),只有拷贝对象中的值受影响,原始对象不会被影响。

    由于下面的案例比较长,主要是为了验证上面的总结,个人建议,结合着上面的总结,把下面的案例自己动手操作一遍,你可以深浅拷贝有更加深刻的了解。

    浅拷贝案例

    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 中的赋值、引用、拷贝、作用域

    深拷贝案例

    由于篇幅有限,深拷贝的案例就不在列举了,有兴趣的可以自己按照浅拷贝的案例方式自己动手实践一下,后面字典自带的拷贝可以实现深拷贝,可以结合下面的案例。

    1. 浅拷贝,除了父层拷贝,对子元素也要进行拷贝(本质上递归浅拷贝)
    2. 经过深拷贝后,原对象和拷贝的新对象所有的元素地址都是不一样的
    3. 可以用分片表达式进行深拷贝
    4. 字典自带的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 方法可以实现深拷贝 
    

    你可能感兴趣的文章

  • 相关阅读:
    web中的懒加载
    数据库表的关系
    struts2的MVC模式
    servlet与tomcat的关系
    servlet解析
    解决Mac外接显示器字体模糊的问题
    insmod: ERROR: could not insert module dm-snapshot.ko: Unknown symbol in module
    linux ssh tunnel
    Permission denied (publickey,gssapi-keyex,gssapi-with-mic).错误的解决
    Best practices for a new Go developer
  • 原文地址:https://www.cnblogs.com/oito/p/12149497.html
Copyright © 2020-2023  润新知