博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python中的对象引用、浅拷贝与深拷贝
阅读量:6581 次
发布时间:2019-06-24

本文共 3529 字,大约阅读时间需要 11 分钟。

最近项目中遇到一个Python浅拷贝机制引起的bug,由于对于Python中对象引用、赋值、浅拷贝/深拷贝机制没有足够的认识,导致调试了很久才发现问题,这里简单记录一下相关概念。

在Python的设计哲学中,Python中的每一个东西都是对象,都有一个ob_refcnt变量,这个变量维护着对对象的引用计数,决定着对象的创建与消亡。

所以在Python程序中,一般的赋值操作其实只是将左值指向了右值的引用,并不会创建新的对象,可以通过id函数查看Python中对象在内存中的唯一标识,以list对象为例,如下代码:

>>> alist=[[1,2],3,4]>>> blist=alist>>> id(alist);id(blist) #alist/blist实际引用内存中的同一个list对象140357688098184140357688098184>>> blist.append(5)>>> blist[[1, 2], 3, 4, 5]>>> alist[[1, 2], 3, 4, 5] #由于实际引用同一个list对象,blist增加一个元素后,alist的取值实际上是完全一样的>>> id(alist);id(blist)140357688098184140357688098184

 

在上面的代码中,将alist的值赋给blist,其实只是把blist指向了alist在内存中的对象,两者引用了同一个list对象,此时如果对blist append一个新元素,由于是指向同一个对象,alist的输出结果一样会变化。

通过slice语法或者copy模块的copy函数,可以实现浅拷贝--

>>> import copy>>> alist=[[1,2],3,4]>>> blist=alist[:]>>> clist=copy.copy(alist)>>> id(alist);id(blist);id(clist) #alist/blist/clist实际已经指向内存中的不同list对象140357691858696140357691897864140357720939912>>> id(alist[0]);id(blist[0]);id(clist[0]) #alist[0]/blist[0]/clist[0]三个子对象依然指向内存中的同一个list对象140357691897800140357691897800140357691897800>>> blist.append(5)>>> blist[[1, 2], 3, 4, 5]>>> alist[[1, 2], 3, 4] #blist对象值的变更,不会再影响到alist和clist>>> clist[[1, 2], 3, 4]>>> alist[0].append('a')>>> alist[[1, 2, 'a'], 3, 4]>>> blist[[1, 2, 'a'], 3, 4, 5] #由于实际引用同一对象,alist[0]子对象值的变更,也会从blist[0]/clist[0]上体现出来>>> clist[[1, 2, 'a'], 3, 4]>>> id(alist[1]);id(blist[1]);id(clist[1])109194881091948810919488

 

可以看到blist和clist本身已经是新的list对象,不再引用alist这个list对象,但是三个list中的子对象还是相同的引用,因为python中的浅拷贝只能拷贝父对象,不会拷贝对象内部的子对象。

通过copy模块中的copy.deepcopy函数可以实现深拷贝:

>>> alist=[[1,2],3,4]>>> blist=copy.deepcopy(alist)>>> id(alist);id(blist) #alist/blist已经引用内存中不同的list对象140357692023560140357691897608>>> blist.append(5)>>> blist[[1, 2], 3, 4, 5]>>> alist[[1, 2], 3, 4] #blist取值的变更,不会影响到alist>>> id(alist[0]);id(blist[0]) #alist{0]/blist[0]两个子对象也已经引用内存中不同的list对象140357691897864140357691896136>>> alist[0].append('a')>>> alist[[1, 2, 'a'], 3, 4]>>> blist[[1, 2], 3, 4, 5] # alist[0]子对象值的变更,也不会再印象到blist[0]的值>>> id(alist[1]);id(blist[1])1091948810919488

 

可以看到,通过copy.deepcopy进行拷贝后,alist和blist指向不同的list对象,同时其子对象alist[0]/blist[0]也指向了不同的list对象,但是alist[1]/blist[1]还是指向相同的对象,这是因为3、4在Python中其实是不可变对象,相当于是常量,在Python中不可变对象只会存在唯一一份,所以无论浅拷贝/深拷贝,都是对同一个不可变对象进行的引用。

对于dict/set这些Python类型对象的赋值操作,也会存在类似的浅拷贝/深拷贝的问题,下面再以dict为例贴一下代码:

引用赋值:

>>> adct={
'd':{1:2}, 3:4}>>> bdct=adct>>> id(adct);id(bdct) #adct/bdct实际引用内存中的同一个dict对象140357688090760140357688090760>>> id(adct['d']);id(bdct['d']) #adct['d']/bdct['d']两个子对象实际引用内存中的同一个dict对象140357691897928140357691897928>>> bdct['d'].update({
'a':'b'})>>> bdct{
'd': {1: 2, 'a': 'b'}, 3: 4}>>> adct{
'd': {1: 2, 'a': 'b'}, 3: 4} #由于实际指向同一个子对象,bdct['d']取值的变更会直接影响到adct的值

copy.copy浅拷贝:

>>> adct={
'd':{1:2}, 3:4}>>> bdct=copy.copy(adct)>>> id(adct);id(bdct) #adct/bdct引用不同的dict对象140357688082888140357720937544>>> id(adct['d']);id(bdct['d']) #adct['d']/bdct['d']两个子对象依然指向内存中同一个dict对象140357688101704140357688101704>>> bdct['d'].update({
'a':'b'})>>> bdct{
'd': {1: 2, 'a': 'b'}, 3: 4}>>> adct{
'd': {1: 2, 'a': 'b'}, 3: 4} #由于实际引用同一个子对象,bdct['d']子对象值的变更会直接影响到adct的值

copy.deepcopy深拷贝:

>>> adct={
'd':{1:2}, 3:4}>>> bdct=copy.deepcopy(adct)>>> id(adct);id(bdct) #adct/bdct本身已经引用不同的dict对象140357691897928140357688094152>>> id(adct['d']);id(bdct['d']) #adct/bdct的子对象引用了不同的dict子对象140357688090760140357688085896>>> bdct['d'].update({
'a':'b'})>>> bdct{
'd': {1: 2, 'a': 'b'}, 3: 4}>>> adct{
'd': {1: 2}, 3: 4} #bdct['d']子对象的变更不会再影响到adct['d']的值

 

转载于:https://www.cnblogs.com/AcAc-t/p/python_object_ref_shallow_deep_copy.html

你可能感兴趣的文章
thinkphp 3.2 增加每页显示条数
查看>>
oracle日常简单数据备份与还原
查看>>
我的友情链接
查看>>
黑马程序员__反射总结
查看>>
Scala学习笔记(5)-类和方法
查看>>
Quartz原理
查看>>
完全卸载oracle|oracle卸载|彻底卸载oracle
查看>>
垃圾收集基础
查看>>
Docker安装及基本命令
查看>>
控制namenode检查点发生的频率
查看>>
Linux存储挂载后,无法正常卸载的解决方法
查看>>
2、递归遍历文件夹下每一个文件
查看>>
Remove auto_increment from Schema Dumps (mysqld...
查看>>
解决activity加上Theme.Translucent.NoTitleBar 页面跳转显示桌面
查看>>
php类库
查看>>
浅谈Java中的对象和引用
查看>>
SQL 注入自我总结
查看>>
Linux线程
查看>>
帝国cms使用集锦
查看>>
Exchange Server 2013 系列八:邮箱服务器角色DAG实战
查看>>