美文网首页
深入浅出Python深拷贝浅拷贝

深入浅出Python深拷贝浅拷贝

作者: 李白开水 | 来源:发表于2020-08-11 15:44 被阅读0次

所谓拷贝,就是复制。
深拷贝,就是复制的比较多,浅拷贝,就是复制的比较少。
在Python中,copy.copy可以完成浅拷贝,copy.deepcopy可以完成深拷贝。

举例 图解

1.赋值

如果 a = [11,22], 把a赋给b:

In [1]: a = [11, 22]
In [2]: b = a

In [3]: a
Out[3]: [11, 22]

In [4]: b
Out[4]: [11, 22]

从结果来看是看不出来a和b是否是同一个,我们用id()函数查看一下a和b的id:

In [5]: id(a)
Out[5]: 2293026594376

In [6]: id(b)
Out[6]: 2293026594376

a 和 b的id相同,说明他们指向了同一块区域,这里说指向,是因为在Python中,实际上是有一个空间存储了[11, 22],在给a赋值的时候,变量名a就指向了[11,22]的空间,当把a赋给b的时候,实际上是让b也指向[11,22]。
画图来说明是这样的:


image.png
2.浅拷贝

在python中,copy.copy()可以用来浅拷贝,copy.deepcopy()可以用来深拷贝。

重新定义了a,b,然后把a和b作为一个列表赋给c。

In [11]: a = [11,22]

In [12]: b = [33,44]

In [13]: c = [a, b]

这时候相当于:


image.png

验证一下:
如果a与c[0]的id相同,就说明他们指向的是相同的区域。

In [14]: id(a)
Out[14]: 2293027197896

In [15]: id(c[0])
Out[15]: 2293027197896

如果现在把c赋给d:

In [16]: d = c

这相当于:


image.png

验证一下:

In [17]: id(d)
Out[17]: 2293026531656

In [18]: id(c)
Out[18]: 2293026531656

如果这时候把c浅拷贝给e:

In [19]: import copy

In [20]: e = copy.copy(c)

这时候,e会指向一块新开辟的区域。


image.png

因为是浅拷贝,所以e的这块区域也会指向c的区域的指向,也就是这样:


image.png

验证:

In [21]: id(e)
Out[21]: 2293027309192

In [22]: id(c)
Out[22]: 2293026531656

In [23]: id(e[0])
Out[23]: 2293027197896

In [24]: id(c[0])
Out[24]: 2293027197896

e和c的地址不同,说明e和c指向的区域不一样,e[0]和c[0]的id相同,说明他们指向了同一块空间。
这就是浅拷贝,它只拷贝了第一层,第二层以下的数据并没有真的“拷贝”出来。
当前的a,b和c,e:

In [25]: a
Out[25]: [11, 22]

In [26]: b
Out[26]: [33, 44]

In [27]: c
Out[27]: [[11, 22], [33, 44]]

In [28]: e
Out[28]: [[11, 22], [33, 44]]

因为是浅拷贝,所以如果a修改了,e也会修改。

In [30]: a
Out[30]: [11, 22, 'hahaha']

In [31]: c
Out[31]: [[11, 22, 'hahaha'], [33, 44]]

In [32]: e
Out[32]: [[11, 22, 'hahaha'], [33, 44]]

a = [11,22] c = [a,b],再来看看深拷贝。

3.深拷贝

如果是深拷贝,就原原本本的把所有层的数据都拷贝一份新的,指向也指向这份新的数据。

In [44]: f = copy.deepcopy(c)

In [45]: c
Out[45]: [[11, 22], [33, 44]]

In [46]: f
Out[46]: [[11, 22], [33, 44]]

这时候的指向是这样的:


image.png

验证:
如果c和f,c[0]和f[0]的地址不同,说明是这样的。

In [47]: id(c)
Out[47]: 2293026784840

In [48]: id(f)
Out[48]: 2293027611400

In [49]: id(c[0])
Out[49]: 2293027354312

In [50]: id(f[0])
Out[50]: 2293027035208

4.总结

所以不管是copy还是deepcopy,都会先创建一份新的空间,而直接赋值b=a则不会。

几个问题

1.问题1

前提如下:

In [53]: a = [11,22]

In [54]: b = [33,44]

In [55]: c = [a, b]

In [56]: d = copy.copy(c)

In [57]: e = copy.deepcopy(c)

如果这时候在c中append一个新的列表[55,66]d和e会如何变化?
查看结果:

In [58]: c.append([55,66])

In [59]: d
Out[59]: [[11, 22], [33, 44]]

In [60]: e
Out[60]: [[11, 22], [33, 44]]

原因图解如下:
一开始的指向是这样的:


image.png

或者把d放在上面更直观一些:


image.png
这时候在c中添加新的列表:
image.png
实际上的这个添加和d和e是没有任何关系的,所以才会有这样的结果,因为d已经拷贝完了,拷贝完之后才添加的,所以d中不会有[55,66]。
问题2

前提:

In [70]: a = (11,22)

In [71]:  b = copy.copy(a)

把a浅拷贝给b,这时候a是一个元组。
来查看a和b的id:

In [73]: id(a)
Out[73]: 2293027979080

In [74]: id(b)
Out[74]: 2293027979080

发现他们的地址是一样的。
所以如果浅拷贝发现拷贝对象是一个元组,就直接不拷贝了,变为了指向。
因为元组是不可变类型,数据不会被修改。
如果是深拷贝:

In [75]: c = copy.deepcopy(a)

这时候查看c的id和a的id:

In [76]: id(c)
Out[76]: 2293027979080

In [77]: id(a)
Out[77]: 2293027979080

他们的地址相同,说明深拷贝也没有拷贝。
再看一种情况:


In [79]: a = [11,22]

In [80]: b = [33,44]

In [81]: c = (a,b)

In [82]: d = copy.copy(c)

In [83]: e = copy.deepcopy(c)

此时a和b都是可变的列表,c是不可变的元组,d有了c的浅拷贝,e有了d的深拷贝。
这时候d和e有没有拷贝c?
来查看一下id:

In [84]: id(c)
Out[84]: 2293028072520

In [85]: id(d)
Out[85]: 2293028072520

In [86]: id(e)
Out[86]: 2293027609416

说明浅拷贝还是没有拷贝,还是指向,但深拷贝拷贝了。
所以:

  • 如果拷贝的是一个元组(第一层是元组),而且元组中都是不可变对象,那么copy模块不管是深拷贝还是浅拷贝都变为了指向(引用)。
  • 如果拷贝的是一个元组(第一层是元组),不管元组中的对象可变还是不可变,浅拷贝都直接指向,而不开辟新的空间了。
  • 如果拷贝的是一个元组(第一层是元组),元组中有可变对象,那么深拷贝还是会开辟新的空间,把每一层的数据都拷贝了。
  • 所以当深拷贝的时候,如果每一层的元素都是不可变对象,那深拷贝也不拷贝了,变为了指向,但是不管哪一层的数据,只要有一个是可变对象,那么深拷贝就会递归的把所有的数据都拷贝一份。
问题3

切片是浅拷贝。

In [88]: a = [11,22]

In [89]: b = [33,44]

In [90]: c = [a,b]

In [91]: d = c[:]

In [92]: id(c)
Out[92]: 2293027355592

In [93]: id(d)
Out[93]: 2293026561416

In [94]: a.append("lalala")

In [95]: a
Out[95]: [11, 22, 'lalala']

In [96]: c
Out[96]: [[11, 22, 'lalala'], [33, 44]]

In [97]: d
Out[97]: [[11, 22, 'lalala'], [33, 44]]
问题4

字典中的拷贝。
字典有一个copy方法,用法如下:

dict.copy()

这个拷贝是一个浅拷贝。

In [100]: first = {"a":[1,2],"b":33}

In [101]: second = first.copy()

查看一下两个字典:

In [102]: first
Out[102]: {'a': [1, 2], 'b': 33}

In [103]: second
Out[103]: {'a': [1, 2], 'b': 33}

如果这时候[11,22]列表中添加一个新的值3,再查看一下:

In [104]: first['a'].append(3)

In [105]: first
Out[105]: {'a': [1, 2, 3], 'b': 33}

In [106]: second
Out[106]: {'a': [1, 2, 3], 'b': 33}

说明是浅拷贝。
补充说明:
字典的key是保存在字典里的,value才是指向。


image.png

补充说明:

在python中,像11这样的值的类型的数据在只会创建一个,所以如果a=[11,22],b=[11,22],实际指向是这样的:

image.png
所以图解的部分画的图并不准确,可以重新看一下~
参考:
https://www.cnblogs.com/shiyublog/p/10809953.html#_label3

相关文章

网友评论

      本文标题:深入浅出Python深拷贝浅拷贝

      本文链接:https://www.haomeiwen.com/subject/zjyhdktx.html