最近项目中遇到一个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对象
140357688098184
140357688098184
>>> blist.append(5)
>>> blist
[[1, 2], 3, 4, 5]
>>> alist
[[1, 2], 3, 4, 5] #由于实际引用同一个list对象,blist增加一个元素后,alist的取值实际上是完全一样的
>>> id(alist);id(blist)
140357688098184
140357688098184

在上面的代码中,将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对象
140357691858696
140357691897864
140357720939912
>>> id(alist[0]);id(blist[0]);id(clist[0]) #alist[0]/blist[0]/clist[0]三个子对象依然指向内存中的同一个list对象
140357691897800
140357691897800
140357691897800
>>> 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])
10919488
10919488
10919488

可以看到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对象
140357692023560
140357691897608
>>> 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对象
140357691897864
140357691896136
>>> 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])
10919488
10919488

可以看到,通过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对象
140357688090760
140357688090760
>>> id(adct['d']);id(bdct['d']) #adct['d']/bdct['d']两个子对象实际引用内存中的同一个dict对象
140357691897928
140357691897928
>>> 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对象
140357688082888
140357720937544
>>> id(adct['d']);id(bdct['d']) #adct['d']/bdct['d']两个子对象依然指向内存中同一个dict对象
140357688101704
140357688101704
>>> 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对象
140357691897928
140357688094152
>>> id(adct['d']);id(bdct['d']) #adct/bdct的子对象引用了不同的dict子对象
140357688090760
140357688085896
>>> bdct['d'].update({'a':'b'})
>>> bdct
{'d': {1: 2, 'a': 'b'}, 3: 4}
>>> adct
{'d': {1: 2}, 3: 4} #bdct['d']子对象的变更不会再影响到adct['d']的值

Python中的对象引用、浅拷贝与深拷贝的更多相关文章

  1. 图解python中赋值、浅拷贝、深拷贝的区别

    Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果.下面本文就通过简单的例子介绍一下这些概念之间的差别. 对象赋值 直接看一段代码: will = ...

  2. Python中赋值、浅拷贝与深拷贝

    python中关于对象复制有三种类型的使用方式,赋值.浅拷贝与深拷贝.他们既有区别又有联系,刚好最近碰到这一类的问题,研究下. 一.赋值 在python中,对象的赋值就是简单的对象引用,这点和C++不 ...

  3. 关于python中赋值、浅拷贝、深拷贝之间区别的深入分析

    当重新学习了计算机基础课程<数据结构和算法分析>后再来看这篇自己以前写的博文,发现错误百出.python内置数据类型之所以会有这些特性,归根结底是它采用的是传递内存地址的方式,而不是传递真 ...

  4. python中赋值、浅拷贝、深拷贝详解(转)

    一.赋值 >>> a = [1, 2, 3]>>> b = a>>> print(id(a), id(b), sep='\n')139701469 ...

  5. python中赋值和浅拷贝与深拷贝

    初学编程的小伙伴都会对于深浅拷贝的用法有些疑问,今天我们就结合python变量存储的特性从内存的角度来谈一谈赋值和深浅拷贝~~~ 预备知识一——python的变量及其存储 在详细的了解python中赋 ...

  6. Python中赋值、浅拷贝和深拷贝的区别

    前言文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http: ...

  7. Python中list的复制及深拷贝与浅拷贝探究

    在Python中,经常要对一个list进行复制.对于复制,自然的就有深拷贝与浅拷贝问题.深拷贝与浅拷贝的区别在于,当从原本的list复制出新的list之后,修改其中的任意一个是否会对另一个造成影响,即 ...

  8. 一入python深似海--浅拷贝与深拷贝

    python中有一个模块copy,deepcopy函数用于深拷贝,copy函数用于浅拷贝. 要理解浅拷贝,必须先弄清楚python中的引用. 引用 Python中一切都是对象,变量中存放的是对象的引用 ...

  9. Python对象赋值、浅拷贝、深拷贝

    Python中,基本数据类型,理解为常见数据类型:布尔型.整型.浮点型.字符串.列表.元组.字典.集合,随语言不同而不同,但是根据在内存中存储方式的不同,区分开原子类型和容器类型. 对象赋值 对象的赋 ...

随机推荐

  1. 使用JAVA进行排序

    利用JAVA完成排序 当我们在进行数据库进行查询的时候,当需要按某个字段来进行排序的时候,可以使用SQL语句来完成排序,可以升序,也可以降序.JAVA中的Collections类也可以完成这种操作,S ...

  2. Java虚拟机6:垃圾收集(GC)-1(内存溢出和内存泄漏的区别)

    1.前言 在进行垃圾收集之前需要普及几个比较重要的概念. 2.内存溢出和内存泄露的概念和区别: (1):内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间可以分配,系统不 ...

  3. POJ 2407 Relatives 欧拉函数题解

    版权声明:本文作者靖心,靖空间地址:http://blog.csdn.net/kenden23/,未经本作者同意不得转载. https://blog.csdn.net/kenden23/article ...

  4. 日常踩坑——rand()总是出现重复数据

    写了一个生成随机数组的函数,然后跑出来,结果总是…… 然后,很奇怪的是一步一步调试,它就没问题了,WTF??? 问题出在:重复写了srand(time(NULL)),只保留一个就好了. int* ge ...

  5. 如何批量下载bing的背景图片?

    工具准备 wget(点击下载) 批处理命令(点击下载) 网友提供的接口:http://area.sinaapp.com/bingImg?daysAgo=1(1代表天数) 实现步骤 1.打开记事本,并将 ...

  6. [Python 多线程] Semaphore、BounedeSemaphore (十二)

    Semaphore 信号量,信号量对象内部维护一个倒计数器,每一次acquire都会减1,当acquire方法发现计数为0就阻塞请求的线程,直到其它线程对信号量release后,计数大于0,恢复阻塞的 ...

  7. SpringBoot实战(十一)之与JMS简单通信

    什么是JMS? 引用百度百科上的说明: JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之 ...

  8. Python自动化之Django中间件

    django中间件 Django请求生命周期 中间件中可以定义方法,分别是 process_request(self,request) process_view(self, request, call ...

  9. SVN篇

    启动SVN : svnserve -d -r svn 查看进程: ps -ef | grep svmserve -------------------------------------------- ...

  10. HDU 1358 Period 求前缀长度和出现次数(KMP的next数组的使用)

    Period Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Subm ...