Python中的深拷贝和浅拷贝
前言
在Python中,经常涉及到数据的传递,在数据传递使用过程中,可能会发生数据被修改的问题。为了防止数据被修改,就需要在传递一个副本,即使副本被修改,也不会影响原数据的使用。为了生成这个副本,就产生了拷贝。今天就说一下Python中的深拷贝与浅拷贝的问题。
一、Python 的引用计数
首先我们要知道,Python 内不可变对象的内存管理方式是引用计数。因此,我们在谈论拷贝时,其实谈论的主要特点都是基于可变对象的。我们来看下面这段代码:
import copy
a = "lisi"
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print ("赋值:", id(b))
print ("浅拷贝:", id(c))
print ("深拷贝:", id(d))
输出结果:
因为我们这里操作的是不可变对象,Python 用引用计数的方式管理它们,所以 Python 不会对值相同的不可变对象,申请单独的内存空间。只会记录它的引用次数。
二、浅拷贝
我们先来比较一下浅拷贝和赋值在可变对象上的区别:
import copy
a = ["lisi"]
b = a
c = copy.copy(a)
print ("赋值:", id(b))
print( "浅拷贝:", id(c))
输出结果:
赋值就是对物体进行贴标签操作,作用于同一物体。而浅拷贝则会创建一个新的对象,至于对象中的元素,它依然会引用原来的物体,我们再来看一段例子:
import copy
a = ["lisi"]
print ("改变前,a内部的元素id:", [id(_) for _ in a])
c = copy.copy(a)
print ("改变前,浅拷贝c内部的元素id:", [id(_) for _ in c])
a[0] = "zhangsan"
print( "改变后,a内部的元素id:", [id(_) for _ in a])
print ("改变后,浅拷贝c内部的元素id:", [id(_) for _ in c])
输出结果:
操作不可变对象时,由于引用计数的特性,被拷贝的元素改变时,就相当于撕掉了原来的标签,重新贴上新的标签一样,对于我们已拷贝的元素没有任何影响。因此在操作不可变对象时,浅拷贝和深拷贝是没有区别的
三、深拷贝
深拷贝遇到可变对象,则又会进行一层对象创建,所以你操作被拷贝对象内部的可变对象,不影响拷贝对象内部的值
import copy
import json
a = [["lis"], "zhangs"]
print ("改变前,a的值", json.dumps(a, ensure_ascii=False))
print ("改变前,a内部的元素id:", [id(_) for _ in a])
d = copy.deepcopy(a)
print ("改变前,d的值", json.dumps(d, ensure_ascii=False))
print ("改变前,深拷贝d内部的元素id:", [id(_) for _ in d])
a[0][0] = "小红"
a[1] = "小明"
print ("改变后,a的值", json.dumps(a, ensure_ascii=False))
print ("改变后,d的值", json.dumps(d, ensure_ascii=False))
print ("改变后,a内部的元素id:", [id(_) for _ in a])
print ("改变后,深拷贝d内部的元素id:", [id(_) for _ in d])
输出结果:
四、总结
- 由于 Python 内部引用计数的特性,对于不可变对象,浅拷贝和深拷贝的作用是一致的,就相当于复制了一份副本,原对象内部的不可变对象的改变,不会影响到复制对象
- 浅拷贝的拷贝。其实是拷贝了原始元素的引用(内存地址),所以当拷贝可变对象时,原对象内可变对象的对应元素的改变,会在复制对象的对应元素上,有所体现
- 深拷贝在遇到可变对象时,又在内部做了新建了一个副本。所以,不管它内部的元素如何变化,都不会影响到原来副本的可变对