Python浅拷贝-深拷贝 解析

浅拷贝

copy.copy()

copy函数是浅拷贝,只对可变类型的第一层对象进行拷贝,对拷贝的对象开辟新的内存空间进行存储,不会拷贝对象内部的子对象

特性: 浅拷贝只会对可变类型第一层进行拷贝;

  • 可变类型:只能作用于列表/字典/集合,而不能拷贝数字/字符串/元组;
  • 第一层:例如一个列表内的嵌套列表无法拷贝[1,2,[a,b],3]。
如何验证?

通过id()对比拷贝前后对象的内存地址,或直接用is方法判断拷贝前后对象内存地址是否相同。

验证示例:
  • 字符串浅拷贝

    import copy
    a = "abc"
    b = copy.copy(a)
    print("a的内存地址:",id(a))
    print("b的内存地址:",id(b))
    

    输出:

    a的内存地址: 2140603563288 
    b的内存地址: 2140603563288
    

    结论:未拷贝

  • 数值浅拷贝

    a = 12
    b = copy.copy(a)
    print("a的内存地址:",id(a))
    print("b的内存地址:",id(b))
    

    输出:

    a的内存地址: 1896907216
    b的内存地址: 1896907216
    

    结论:未拷贝

  • 列表浅拷贝

    a = [1,2,3]
    b = copy.copy(a)
    print("a的内存地址:",id(a))
    print("b的内存地址:",id(b))
    

    输出:

    a的内存地址: 2140688273224
    b的内存地址: 2140681134216
    

    结论:拷贝成功,创建了新的内存地址

  • 列表嵌套情况浅拷贝(特别注意)

    a = [1,2,[3,4]]
    b = copy.copy(a)
    print("a:",a)
    print("a的内存地址:",id(a))
    print("b:",b)
    print("b的内存地址:",id(b))
    

    输出:

    a: [1, 2, [3, 4]]
    a的内存地址: 2140679959240
    b: [1, 2, [3, 4]]
    b的内存地址: 2140679959112
    

    外部地址改变,创建了新的内存地址

    验证内部嵌套列表是否拷贝:

    In [43]: id(a[2])
    Out[43]: 2140680066952
    
    In [44]: id(b[2])
    Out[44]: 2140680066952
    

    结论:嵌套列表内存地址相同,未进行拷贝

    再次验证,尝试修改a列表,是否对b列表造成影响:

    In [45]: a.append(5)
    In [46]: a[2].append("a")
    In [48]: b
        
    Out[48]: [1, 2, [3, 4, 'a']]
    

    分析:a列表修改外层列表不影响b列表;修改a列表的子列表会同时改变b列表的子列表。

    结论

    符合浅拷贝的特性,浅拷贝只会对可变类型第一层进行拷贝,而不会拷贝其可变类型的子对象,因此未拷贝部分指向的仍是同一个内存地址;

深拷贝

copy.deepcopy

deepcopy函数是深拷贝, 只要发现对象有可变类型就会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。

深拷贝同样无法拷贝不可变类型:字符串、数字、元组。

  • 不可变类型的深拷贝(主要讨论元组及其子元素)
# 不可变类型元组(需要特别注意)  
In [59]: a = (1,2)

In [60]: b = copy.deepcopy(a)

In [61]: id(a)
Out[61]: 2140688276424

In [62]: id(b)
Out[62]: 2140688276424
      
In [63]: a = (1,2,[1,3])

In [64]: b =copy.deepcopy(a)

    
# 此处虽然外层元组是不可变类型,但内存地址依然改变了,原因是深拷贝会对所有可变子对象进行拷贝,因此内部列表会被拷贝,内存地址改变
In [65]: id(a)
Out[65]: 2140685431720

In [66]: id(b)
Out[66]: 2140688106408
    
# 其内部的子列表被拷贝,内存地址改变,由于元组是不可变类型,内部改变,其本身地址也会改变。
In [67]: id(a[2])
Out[67]: 2140681195272

In [68]: id(b[2])
Out[68]: 2140687283336
# 其内部的不可变对象无法拷贝,内存地址不变
In [69]: id(a[1])
Out[69]: 1896906896

In [70]: id(b[1])
Out[70]: 1896906896
  

结论:

不可变类型进行深拷贝如果子对象没有可变类型则不会进行拷贝,而只 是拷贝了这个对象的引用,否则会对该对象到最后一个可变类型的每一层 对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储

  • 可变类型的深拷贝(主要讨论列表及子列表)

    In [77]: a = [1,2,[1,2,3]]
    
    In [78]: b = copy.deepcopy(a)
    # 外部列表内存地址改变
    In [79]: id(a)
    Out[79]: 2140687234824
    
    In [80]: id(b)
    Out[80]: 2140681187208
    
    # 子列表内存地址也改变
    In [81]: id(a[2])
    Out[81]: 2140681025608
    
    In [82]: id(b[2])
    Out[82]: 2140690147272
        
    # 改变子列表元素不会再影响deepcoy的子列表元素
    In [83]: a[2].append(3)
    In [84]: a
    Out[84]: [1, 2, [1, 2, 3, 3]]
        
    In [85]: b
    Out[85]: [1, 2, [1, 2, 3]]
    
    

    结论:

    可变类型进行深拷贝会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。

总结

  • 浅拷贝使用copy.copy函数
  • 深拷贝使用copy.deepcopy函数
  • 不管是给对象进行深拷贝还是浅拷贝,只要拷贝成功就会开辟新的内存空间存储拷贝的对象。
  • 浅拷贝和深拷贝的区别是:
    • 浅拷贝最多拷贝对象的一层,深拷贝可能拷贝对象的多层。

你可能感兴趣的:(Python浅拷贝-深拷贝 解析)