首先要了解什么是拷贝、浅拷贝、深拷贝?
拷贝:
从原始数据复制一份出来,当复制成功后,这两份数据都是相互独立的,即修改任意一份数据都不会影响另一份数据。
浅拷贝:
python中,浅拷贝就是只是拷贝最外层的类型,简单来讲就是拷贝了引用,并没有拷贝内容. copy.copy()
深拷贝:
对于一个对象所有层次的拷贝(递归拷贝)copy.deepcopy()
要知道深浅拷贝的区别,首先要知道python中什么是 可变数据类型 和 不可变数据类型
不可变数据类型的定义:
python中的不可变数据类型,不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象,内部会有一个引用计数来记录有多少个变量引用这个对象.
python中 不可变数据类型:
- 整型
- 浮点数
- 布尔值
- 字符串
- 元组
可变数据类型的定义:
可变数据类型,允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址, 相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。
python中 可变数据类型:
- 列表
- 字典
通过python中数据类型的分类,我们谈论以下几种的拷贝:
不可变数据类型:
- 赋值
- 浅拷贝
- 深拷贝
可变数据类型:
- 赋值
- 浅拷贝
- 深拷贝
不可变数据类型
1. 赋值
我们已知python中不可变数据类型:整型、浮点数、字符串、布尔值、元组
In [1]: a1 = 123 # 整型 In [2]: a2 = a1 In [3]: id(a1), id(a2) Out[3]: (1562980880, 1562980880) In [4]: b1 = 1.123 # 浮点数 In [5]: b2 = b1 In [6]: id(b1), id(b2) Out[6]: (1503028953024, 1503028953024) In [7]: c1 = 'hello' # 字符串 In [8]: c2 = c1 In [9]: id(c1), id(c2) Out[9]: (1503040484272, 1503040484272) In [10]: d1 = True # 布尔值 In [11]: d2 = d1 In [12]: id(d1), id(d2) Out[12]: (1562722720, 1562722720) In [13]: e1 = (1, 2, 3, 'hkey') # 元组 In [14]: e2 = e1 In [15]: id(e1), id(e2) Out[15]: (1503040349032, 1503040349032)
通过以上的例子,a1、a2 赋值的值是一样的。因为python有一个重用机制,对于 不可变数据类型 来说,python并不会开辟一块新的内存空间,而是维护同一块内存地址,只是将 不可变数据类型 对应的地址引用赋值给变量a1、a2。所以根据输出结果,a1和a2其实对应的是同一块内存地址,只是两个不同的引用。
结论:对于 不可变数据类型 通过'='赋值,不可变数据类型在内存当中用的都是同一块地址。
2. 浅拷贝
In [1]: import copy In [2]: a1 = 3.14 In [3]: a2 = copy.copy(a1) In [4]: id(a1), id(a2) Out[4]: (1690132070168, 1690132070168)
通过使用copy模块里的copy()函数来进行浅拷贝,把a1拷贝一份赋值给a2,查看输出结果发现,a1和a2的内存地址还是一样。
结论:对于 不可变数据类型 通过浅拷贝,不可变数据类型在内存当中用的都是同一块地址。
3. 深拷贝
In [1]: import copy In [2]: a1 = 'hello' In [3]: a2 = copy.deepcopy(a1) In [4]: id(a1), id(a2) Out[4]: (1645307287064, 1645307287064)
通过使用copy模块里的deepcopy()函数来进行深拷贝,把a1拷贝一份赋值给a2,查看输出结果发现,a1和a2的内存地址还是一样。
结论:对于 不可变数据类型 通过深拷贝,不可变数据类型在内存当中用的都是同一块地址。
这里可以对 不可变数据类型 下一个定义了:
对于python中 不可变数据类型 的赋值、浅拷贝、深拷贝在内存中都是指向同一内存地址。如下图:
可变数据类型
1. 赋值
In [1]: n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} In [2]: n2 = n1 In [3]: id(n1), id(n2) Out[3]: (1888346752712, 1888346752712)
在上面的例子中,我们使用了列表嵌套列表的方式,赋值后内存空间地址是一致的。其中原理如下图:
结论:对于 可变数据类型 进行赋值内存地址是不会变化的。
2. 浅拷贝
import copy n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} n3 = copy.copy(n1) # 浅拷贝 print("第一层字典的内存地址:") print(id(n1)) print(id(n3)) print("第二层嵌套的列表的内存地址:") print(id(n1["k3"])) print(id(n3["k3"])) 执行结果: 第一层字典的内存地址: 2260623665800 2260625794440 第二层嵌套的列表的内存地址: 2260626131144 2260626131144
通过以上结果可以看出,进行浅拷贝时,我们的字典第一层n1和n3指向的内存地址已经改变了,但是对于第二层里的列表并没有拷贝,它的内存地址还是一样的。原理如下图:
结论:对于 python 可变数据类型,浅拷贝只能拷贝第一层地址。
3. 深拷贝
import copy n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]} n4 = copy.deepcopy(n1) # 深拷贝 print("第一层字典的内存地址:") print(id(n1)) print(id(n4)) print("第二层嵌套的列表的内存地址:") print(id(n1["k3"])) print(id(n4["k3"])) 执行结果: 第一层字典的内存地址: 2018569398920 2018574527176 第二层嵌套的列表的内存地址: 2018576058568 2018576056840
通过以上结果发现,进行深拷贝时,字典里面的第一层和里面嵌套的地址都已经变了。对于深拷贝,它会拷贝多层,将第二层的列表也拷贝一份,
如果还有第三层嵌套,那么第三层的也会拷贝,但是对于里面的最小元素,比如数字和字符串,这里就是“wu”,123,“alex”,678之类的,
按照python的机制,它们会共同指向同一个位置,它的内存地址是不会变的。原理如下图:
结论:对于 python 可变数据类型,它里面嵌套多少层,就会拷贝多少层出来,但是最底层的数字和字符串不变。
python 深浅拷贝的实例:
我们在维护服务器信息的时候,经常会要更新服务器信息,这时我们重新一个一个添加是比较麻烦的,我们可以把原数据类型拷贝一份,在它的基础上做修改。
实例1:使用浅拷贝
import copy # 定义了一个字典,存储服务器信息 dic = { 'cpu':[80, ], 'mem':[80, ], 'disk':[80, ] } print('before', dic) new_dic = copy.copy(dic) new_dic['cpu'][0] = 50 # 更新 cpu 为 50 print(dic) print(new_dic) 执行结果: before {'cpu': [80], 'mem': [80], 'disk': [80]} {'cpu': [50], 'mem': [80], 'disk': [80]} {'cpu': [50], 'mem': [80], 'disk': [80]}
这时我们会发现,使用浅拷贝时,我们修改新的字典的值之后,原来的字典里面的cpu值也被修改了,这并不是我们希望看到的。
实例2:使用深拷贝
import copy # 定义了一个字典,存储服务器信息 dic = { 'cpu':[80, ], 'mem':[80, ], 'disk':[80, ] } print('before', dic) new_dic = copy.deepcopy(dic) new_dic['cpu'][0] = 50 # 更新 cpu 为 50 print(dic) print(new_dic) 执行结果: before {'cpu': [80], 'disk': [80], 'mem': [80]} {'cpu': [80], 'disk': [80], 'mem': [80]} {'cpu': [50], 'disk': [80], 'mem': [80]}
使用深拷贝的时候,发现只有新的字典的cpu值被修改了,原来的字典里面的cpu值没有变。大功告成!