Python 深拷贝、浅拷贝
仅供学习参考,转载请注明出处
深拷贝、浅拷贝
1. 浅拷贝
浅拷贝是对于一个对象的顶层拷贝
通俗的理解是:拷贝了引用,并没有拷贝内容
使用ipython3编写几个示例来看看:
In [1]: a = [1,2,3,4]
In [2]: b = a
In [3]: a
Out[3]: [1, 2, 3, 4]
In [4]: b
Out[4]: [1, 2, 3, 4]
In [5]: id(a) # 查看变量a的内存地址
Out[5]: 140490275823112
In [6]: id(b) # 查看变量b的内存地址
Out[6]: 140490275823112
In [7]: import copy
In [8]:
In [8]: c = copy.copy(a) # 使用copy拷贝 a
In [9]: c
Out[9]: [1, 2, 3, 4]
In [10]: id(c) # 查看变量c的内存地址
Out[10]: 140490271207112
In [11]:
从上面的示例来看,b = a
与 c = copy.copy(a)
这两种方式下,b 与 a 的内存地址都是 140490275823112
,但是 c 的内存地址却是 140490271207112
。c 已经指向了另一个内存地址了。
说明: b = a
符合浅拷贝的规则。
思考:既然浅拷贝都是指向同一个内存地址,那么是不是修改一个变量的话,是不是另一个变量指向的值都会一起修改呢?
# 首先查看一下上一个步骤之后, a b c 三个变量的值
In [11]: c
Out[11]: [1, 2, 3, 4]
In [12]: a
Out[12]: [1, 2, 3, 4]
In [13]: b
Out[13]: [1, 2, 3, 4]
# 因为 a b 两个变量都是指向同一个内存地址,那么给 b 的list增加一个 5,查看一下变量的修改
In [14]: b.append(5)
In [15]: b
Out[15]: [1, 2, 3, 4, 5]
In [16]: a
Out[16]: [1, 2, 3, 4, 5]
In [17]: c
Out[17]: [1, 2, 3, 4]
# 可以从上面三个变量看出,a与b变量是同时修改了,而c因为不同内存地址,所以并没有修改。
# 那么修改变量c,增加一个数字6到list中,当然是不会影响变量a与b的,实践看看。
In [18]: c.append(6)
In [19]: c
Out[19]: [1, 2, 3, 4, 6]
In [20]: a
Out[20]: [1, 2, 3, 4, 5]
In [21]: b
Out[21]: [1, 2, 3, 4, 5]
In [22]:
注意: 其实上面的理解对于浅拷贝是有一定的偏差的,虽然 b = a 的确属于浅拷贝的一种,但是浅拷贝 c = copy.copy(a) 也是属于浅拷贝的另一种,那么为什么内存不一样呢?
其实浅拷贝只是拷贝最上面的那一层数据,其实也是会生成一个新的变量,此时内存就会不一样。下面再来一个示例演示一下:
In [22]: d = [a,b] # 首先设置变量 d 加入 a 与 b
In [23]: e = copy.copy(d) # 使用 e 浅拷贝 d,此时就会生成一个新的内存变量 e
In [24]: id(d) # 查看d的变量内存
Out[24]: 140490271478024
In [25]: id(e) # 查看e的变量内存,果然是跟 d 不同的。但是e 浅拷贝过来的 a 与 b 的地址呢?
Out[25]: 140490295815880
In [26]: id(d[0]) # 首先查看 变量 a 在 d 的list地址
Out[26]: 140490275823112
In [27]: id(e[0]) # 查看变量 a 在 e 的list内存地址,居然是一样的。
Out[27]: 140490275823112
In [28]: id(d[1])
Out[28]: 140490275823112
In [29]: id(e[1])
Out[29]: 140490275823112
In [30]:
从上面的结果来看,c 与 d 的变量内存地址不一样,但是 c 与 d 里面的 a 和 b 的内存地址是一样的。
那么是不是就是如果修改 a ,那么 c 与 d 会同时修改呢?
In [30]: a.append(7)
In [31]: a
Out[31]: [1, 2, 3, 4, 5, 7]
In [32]: b
Out[32]: [1, 2, 3, 4, 5, 7]
In [33]: e[0]
Out[33]: [1, 2, 3, 4, 5, 7]
In [34]: d[0]
Out[34]: [1, 2, 3, 4, 5, 7]
In [35]:
答案是会同时修改的,因为内存地址都一致。
这里提供一个理解示意图:
2. 深拷贝
深拷贝是对于一个对象所有层次的拷贝(递归)
In [35]: a = [11,22]
In [36]: b = copy.deepcopy(a) # 对a指向的列表进行深copy
In [37]: a
Out[37]: [11, 22]
In [38]: b
Out[38]: [11, 22]
In [39]: id(a) # 查看 a 的内存地址
Out[39]: 140490295123912
In [40]: id(b) # 查看 b 的内存地址,可以看出与变量 a 是不一致的。
Out[40]: 140490295270088
In [43]: a.append(33) # 那么 a 增加一个变量 33 肯定不会影响 b,执行看看
In [44]: a
Out[44]: [11, 22, 33]
In [45]: b
Out[45]: [11, 22]
In [46]:
但是从这个例子,看不出深拷贝特殊之处。
进一步理解深拷贝
从前面浅拷贝的例子中,我们来看看使用深拷贝有什么变化。
In [46]: a = [1,2,3,4]
In [47]: b = a
In [48]: d = [a,b]
In [49]: e = copy.deepcopy(d) # 使用 e 深拷贝 d ,此时会深度递归 d 里面的所有变量
In [50]: id(d) # 查看变量 d 的内存
Out[50]: 140490296753416
In [51]: id(e) # 查看变量 e 的内存,可以看出 e 与 d 的内存地址是不同的。
Out[51]: 140490295210696
# 前面案例中,d[0] 的内存地址 是 与 c[0] 的内存地址是一样的,这里来看深拷贝是不一样
In [52]: id(d[0])
Out[52]: 140490271451720
In [53]: id(e[0])
Out[53]: 140490294520008
In [54]: id(e[1])
Out[54]: 140490294520008
In [55]: id(d[1])
Out[55]: 140490271451720
# 从上面打印来看,深拷贝后,e 与 d 的所有内存变量都是不同的了。
In [56]: d
Out[56]: [[1, 2, 3, 4], [1, 2, 3, 4]]
In [57]: e
Out[57]: [[1, 2, 3, 4], [1, 2, 3, 4]]
In [58]: a.append(5)
In [59]: d
Out[59]: [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
In [60]: e
Out[60]: [[1, 2, 3, 4], [1, 2, 3, 4]]
In [61]:
3. 拷贝的其他方式
分片表达式
d = c[:]
可以赋值一个序列
In [1]: a = [11,22]
In [2]: b = [33,44]
In [3]: c = [a,b]
In [4]: d = c[:] # 分片表达式
In [5]: c
Out[5]: [[11, 22], [33, 44]]
In [6]: d
Out[6]: [[11, 22], [33, 44]]
# 查看使用分片表达式传值后的变量内存,可以看出 c 与 d 是不同的。
In [7]: id(c)
Out[7]: 140089352809416
In [8]: id(d)
Out[8]: 140089352792904
# 那么看看 c[0] 与 d[0] 的内存变量,从结果来看是相同的。
In [9]: id(c[0])
Out[9]: 140089352742024
In [10]: id(d[0])
Out[10]: 140089352742024
# 那么既然内存地址都相同,那么给a增加一个33的变量,查看是否同时修改值
In [11]: a
Out[11]: [11, 22]
In [12]: a.append(33)
In [13]: a
Out[13]: [11, 22, 33]
In [14]: c
Out[14]: [[11, 22, 33], [33, 44]]
In [15]: d
Out[15]: [[11, 22, 33], [33, 44]]
In [16]:
从上面的结果来看,分片表达式就是一种浅拷贝。
字典的copy方法可以拷贝一个字典
In [16]: import copy
# 创建一个字典
In [17]: d = dict(name="zhangsan",age=27)
# 将字典d 拷贝到 co
In [18]: co = d.copy()
# 查看一下 d 与 co 的值
In [19]: d
Out[19]: {'name': 'zhangsan', 'age': 27}
In [20]: co
Out[20]: {'name': 'zhangsan', 'age': 27}
# 查看一下 d 与 co的内存值,可以看出是不同的。
In [21]: id(d)
Out[21]: 140089352402120
In [22]: id(c)
Out[22]: 140089352809416
In [23]:
# 那么直接给 d 设置新的字典内容
In [23]: d = dict(name="zhangsan",age=27,children_ages = [11,22])
In [24]: d
Out[24]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22]}
In [26]: co
Out[26]: {'name': 'zhangsan', 'age': 27}
# 重新将 d 拷贝到 co
In [27]: co = d.copy()
In [28]: co
Out[28]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22]}
# 给字典里面的 children_ages 增加 list 数字 9
In [29]: d["children_ages"].append(9)
# 可以看到 co 也跟着一起变化了,说明 children_ages 的内存地址是一致的。
In [30]: d
Out[30]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22, 9]}
In [31]: co
Out[31]: {'name': 'zhangsan', 'age': 27, 'children_ages': [11, 22, 9]}
In [32]:
In [32]: id(d["children_ages"])
Out[32]: 140089267051336
In [33]: id(co["children_ages"])
Out[33]: 140089267051336
In [34]:
4. 注意点
浅拷贝对不可变类型和可变类型的copy不同
- copy.copy对于可变类型,会进行浅拷贝
- copy.copy对于不可变类型,不会拷贝,仅仅是指向
# 拷贝list可变类型
In [34]: a = [11,22,33]
In [35]: b = copy.copy(a)
In [36]: id(a)
Out[36]: 140089256561608
In [37]: id(b)
Out[37]: 140089352086664
In [38]: a.append(44)
In [39]: a
Out[39]: [11, 22, 33, 44]
In [40]: b
Out[40]: [11, 22, 33]
In [41]:
# 使用元祖再来演示一下,可以看出拷贝的两个变量内存地址一致。
In [41]: a = (11,22,33)
In [42]: b = copy.copy(a)
In [43]: id(a)
Out[43]: 140089283270624
In [44]: id(b)
Out[44]: 140089283270624
In [45]:
copy.copy和copy.deepcopy的区别
copy.copy
In [45]: a = [11,22]
# 使用元组来括起来 a ,那么后续的内存地址就不会变
In [46]: b = (a, )
In [47]: b
Out[47]: ([11, 22],)
In [48]: c = [b,]
In [49]: c
Out[49]: [([11, 22],)]
# 使用 d 浅拷贝 c,那么 c 里面的元组内存地址会不会变化呢?
In [50]: d = copy.copy(c)
In [51]: d
Out[51]: [([11, 22],)]
# 首先查看一下变量 c 与 d 的内存地址,发现是不同的,正常。
In [52]: id(c)
Out[52]: 140089267047816
In [53]: id(d)
Out[53]: 140089352213384
# 查看 c 与 d 的元组内存地址,发现是一样的,那么就是最初的变量 a 的地址
In [57]: id(c[0])
Out[57]: 140089352719216
In [58]: id(d[0])
Out[58]: 140089352719216
# 给变量 a 增加一个 33的数字,那么 c 与 d 就会同时一起增加,如下:
In [54]: a.append(33)
In [55]: c
Out[55]: [([11, 22, 33],)]
In [56]: d
Out[56]: [([11, 22, 33],)]
来看看完成使用可变的list示例
In [60]: a = [11,22]
In [61]: b = [a]
In [62]: b
Out[62]: [[11, 22]]
In [63]: c = [b]
In [64]: c
Out[64]: [[[11, 22]]]
In [65]: d = copy.copy(c)
In [66]: d
Out[66]: [[[11, 22]]]
In [67]: id(d)
Out[67]: 140089282147016
In [68]: id(c)
Out[68]: 140089352347848
In [69]: id(d[0])
Out[69]: 140089356847432
In [70]: id(c[0])
Out[70]: 140089356847432
In [71]: id(d[0][0])
Out[71]: 140089282502536
In [72]: id(c[0][0])
Out[72]: 140089282502536
In [73]: c
Out[73]: [[[11, 22]]]
In [74]: d
Out[74]: [[[11, 22]]]
In [75]: a
Out[75]: [11, 22]
In [76]: a.append(33)
In [77]: c
Out[77]: [[[11, 22, 33]]]
In [78]: d
Out[78]: [[[11, 22, 33]]]
In [79]: a
Out[79]: [11, 22, 33]
In [80]:
从上面的操作来看,只要是浅拷贝可变的变量,那么内部的数据都是会指向同一个内存地址的。
copy.deepcopy
关注微信公众号,回复【资料】、Python、PHP、JAVA、web,则可获得Python、PHP、JAVA、前端等视频资料。