所谓拷贝,就是复制。
深拷贝,就是复制的比较多,浅拷贝,就是复制的比较少。
在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],实际指向是这样的:
所以图解的部分画的图并不准确,可以重新看一下~
参考:
https://www.cnblogs.com/shiyublog/p/10809953.html#_label3
网友评论