001 说说Python中的深拷贝和浅拷贝
在Python编程中忽略深拷贝和浅拷贝可能会造成未知的风险。
比如我们打算保存一份原始对象的副本作为上一状态的记录,此后修改原始对象数据时,若是副本对象的数据也发生改变,那么这就是一个严重的错误。
注:这里我们称被拷贝的对象为原始对象,拷贝生成的副本称为副本对象
下面我们看个例子:
# :假设cuboid中保存了两个长方体的长、宽、高。 >>> cuboid= [[1, 2, 3], [2, 3, 4]] # :有多种方式生成cuboid的副本,这里使用list()方法。 >>> cuboid_tmp = list(cuboid) >>> cuboid_tmp [[1, 2, 3], [2, 3, 4]] # :修改原始对象数据。 >>> cuboid[0][1] = 10 >>> cuboid [[1, 10, 3], [2, 3, 4]] # :查看副本对象中数据并与原始对象数据对比。 >>> cuboid_tmp [[1, 10, 3], [2, 3, 4]]
上面例子中,副本对象中数据被原始对象中数据影响了,此时我们怀疑原始对象和副本对象是不是引用地址相同?
我们看一下这两个对象的地址。
>>> id(cuboid) 2827786472008 >>> id(cuboid_tmp) 2827786472200 # 不一样
结果证明,并不是,其实list()方法就是一种浅拷贝方式,只有深入理解深拷贝和浅拷贝才能防止此种错误的发生。
下面是我整理的深浅拷贝和赋值的知识点。(虽然赋值操作不是拷贝,但是这里也有必要说一下。)
![](https://img2020.cnblogs.com/blog/1805906/202103/1805906-20210302170744398-377648186.png)
浅拷贝
一句话来概括浅拷贝就是,原始对象和副本对象还是藕断丝连。
浅拷贝对于要拷贝的数据是否是复合数据,表现又不同。
我们看下面非复合数据的例子。
# :假设cuboid中保存了一个长方体的长、宽、高。 >>> cuboid= [1, 2, 3] # :我们还是使用list()浅拷贝生成一个副本。 >>> cuboid_tmp = list(cuboid) # :修改原始对象数据。 >>> cuboid[1] = 10 >>> cuboid [1, 10, 3] # :查看副本对象中数据并与原始对象数据对比。 >>> cuboid_tmp [1, 2, 3]
咦!不是说list()是浅拷贝吗?为什么此时副本对象的数据没有发生更改呢。
不急,我们再看一下原始对象和副本对象的地址。
>>> id(cuboid) 2827786471176 >>> id(cuboid_tmp) 2827786484936 # 不一样
我们再来看看原始对象和副本对象中每个数据的地址。
>>> for i in cuboid: ... print(id(i)) 140733841519440 140733841519728 140733841519504 >>> >>> for i in cuboid_tmp: ... print(id(i)) 140733841519440 # 一样 140733841519472 # 不一样 140733841519504 # 一样
我们发现除了修改后的第二个元素地址不一样外,其它元素地址是相同的。
这些说明了浅拷贝后,原始对象和副本对象中元素引用指向相同的地址。
当修改原始对象中某个元素的值时,其对应元素的引用地址发生了改变,但是副本对象中不会受到影响,所以才产生了上面的结果。
下面我们通过图更加深刻的理解非复合数据浅拷贝后原始对象和副本对象的关系。
回到文章最开头的例子,同样是浅拷贝,为什么复合数据的浅拷贝,修改原始数据,可能对副本数据造成影响。(这里用的可能,因为修改原始对象不一定就会修改副本对象)
我们再来通过绘图分析。
浅拷贝只是拷贝第一层元素对象(子列表),即原始对象和副本对象中元素都是指向相同的子列表,子列表中数据元素发生变化不影响子列表的索引位置。
针对浅拷贝,我们完全可以将复合数据抽象成非复合数据去思考,然后参考非复合数据的增删改来思考原始对象和副本对象是否会互相影响。
深拷贝
深拷贝更好理解,我们只需要知道原始对象和副本对象完全不相关,不会互相影响就行。
赋值
我们经常会在编程过程中使用赋值操作,赋值操作类似于windows中创建快捷方式,原始引用和副本引用指向同一个地址。
>>> cuboid = [1,2,3] >>> cuboid_tmp = cuboid >>> id(cuboid) 1919680744520 >>> id(cuboid_tmp) 1919680744520 # 一样
所以说当我们改变原始引用中数据时,副本引用指向的数据也会发生改变,因为两个引用指向的同一个地址。
>>> cuboid[1] = 10 >>> cuboid [1, 10, 3] >>> cuboid_tmp [1, 10, 3]