Python的浅复制与深复制

  • 对于不可变对象来说,因其值不可变,复制一个副本对它的作用不大。但对于可变对象来说,因其值可变,很多时候复制一个副本可以保存它未改变前的值,用来做对比或者做数据备份。
  • 创建一个副本,这里涉及到浅复制和深复制。

浅复制与深复制的构造方法

  • 首先都需要导入copy模块
  • 浅复制:copy.copy(x)
  • 深复制:copy.deepcopy(x)

其中浅复制还可以用切片实现:

  • 例如:a = [1,2,3] ; aa = a[:]

浅复制与深复制的区别

  • 浅复制:复制内层容器时不会单独开辟空间,而是引用原来的地址。
  • 深复制:复制内层不可变的容器时不会单独开辟空间,引用其原地址;若是内层可变的容器,则会单独开辟空间

容器:

  • 可以在里面装下多个元素的,可以用in, not in关键字判断元素是否包含在容器中的。
  • 常见的容器有:字符串、元组、列表、字典、集合。

http://www.pythontutor.com 是一个可以一边执行代码一边查看对象引用情况的网站。
我们构造一个含有内层容器的对象[0,1,[2,99],4],然后在线查看深、浅复制分别都是如何复制的。

1
2
3
4
5
6
7
8
9
#构造对象,并进行浅复制和深复制
import copy
a = [0,1,(2,[9,8]),4]
aa = copy.copy(a) #切片也是浅复制a[:]
aaa = copy.deepcopy(a)

print(id(a[2][1]))
print(id(aa[2][1]))
print(id(aaa[2][1]))

那么,改变原值时,浅复制和深复制的内容会如何改变?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#构造含内层容器的对象
import copy
a = [0,1,[2,99],4]#
aa = copy.copy(a)
aaa = copy.deepcopy(a)

#改变内层容器的值
print(id(a))
a[2][0] = 8
print(id(a[2][0]))
print(id(aa[2][0]))
print(id(aaa[2][0]))
# print(id(a[2][0]) == id(aa[2][0]))
# print(id(a[2][0]) == id(aaa[2][0]))
  • 改变原值的内层容器里的值时,浅复制会随之改变,深复制则不会。

理解深浅复制的区别:

  • 现给出一个列表lsa = [12, 'abc', [8, 0]],对lsa分别进行浅复制赋值给lsb,进行深复制赋值给lsc,然后改lsa中的[8, 0][9, 0],请问lsblsc中的值改变了吗?为什么?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    list_a = [1, "hello", [2, 3]]
    print(id(list_a[2]))
    list_b = copy.copy(list_a)
    print(id(list_b[2]))
    list_c = copy.deepcopy(list_a)
    print(id(list_c[2]))

    list_a[2] = [4,5]
    print(id(list_a[2]))
    print(id(list_b[2]))
    print(id(list_c[2]))

    在执行上述操作后,lsb中的子列表会随之改变,而lsc中的子列表则保持不变。具体分析如下:

    • lsb = copy.copy(lsa)创建了列表的浅副本。浅复制会复制顶层对象,但嵌套的子对象(如列表[8, 0])仍指向原对象。因此,lsblsa的顶层元素是独立的,但它们共享嵌套的子列表。
    • lsc = copy.deepcopy(lsa)创建了列表的深副本。深复制会递归复制所有嵌套对象,生成完全独立的对象树。因此,lsc中的子列表是lsa中子列表的独立副本。

    所以执行当执行lsa[2] = [9, 0]时:

    • lsb的变化lsb的顶层结构与lsa分离,但共享子列表。修改lsa[2]只是替换了lsa的第三个元素的引用,而lsb[2]仍指向原嵌套列表[8, 0]。因此,lsb中的值不会改变
    • lsc的变化: 深复制生成的lsc完全独立于lsa。修改lsa[2]不会影响lsc的任何元素。因此,lsc中的值保持不变

    结论:

    • lsb中的值[8, 0](未改变)
    • lsc中的值[8, 0](未改变)
    • 原因:浅复制共享嵌套对象,而深复制完全独立。修改lsa的子列表引用不会传播到复制对象。