最近项目中遇到一个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. 51nod 1967路径定向(dfs、欧拉回路)

    1967 路径定向 基准时间限制:1.2 秒 空间限制:262144 KB 分值: 80 难度:5级算法题 给出一个有向图,要求给每条边重定向,使得定向后出度等于入度的点最多,输出答案和任意一种方案 ...

  2. BZOJ 1008 越狱 组合数学

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1008 题目大意: 监狱有连续编号为1...N的N个房间,每个房间关押一个犯人,有M种宗 ...

  3. BZOJ3926:[ZJOI2015]诸神眷顾的幻想乡(广义SAM)

    Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看. ...

  4. python 打包文件

    tarfile import tarfile tar = tarfile.open("sk_camera_6018.tar","w") tar.add(full ...

  5. 【nodejs】学习笔记

    学习链接:http://www.cnblogs.com/zhongweiv/p/nodejs_environment.html (一)简介及环境安装 Node.js是让Javascript脱离浏览器运 ...

  6. MyBatis(1)-简单入门

    简介 什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.My ...

  7. ethersjs运行时出现的问题

    1.在运行部署在ganache1.1.0上的合约中的某个函数时,出现了这样的问题,之前部署在geth上时并没有这样的问题 userdeMacBook-Pro:test-ethers user$ nod ...

  8. ERDAS IMAGINE 2014 32位 破解安装

    1.        安装Install ERDAS Foundation 2014 2.        安装ERDAS IMAGINE 2014  32位 3.        安装Intergraph ...

  9. nRF5 SDK for Mesh(五) Light switch demo 点灯例子

    Light switch demo  灯开demo   Purpose This demo project consists of four sub examples - The light swit ...

  10. TTL电平, RS232电平以及CMOS电平的区别

    TTL电平标准 输出 L: <0.8V : H:>2.4V. 输入 L: <1.2V : H:>2.0V TTL器件输出低电平要小于0.8V,高电平要大于2.4V.输入,低于1 ...