浅谈Python浅拷贝、深拷贝及引用机制


Posted in Python onDecember 15, 2016

这礼拜碰到一些问题,然后意识到基础知识一段时间没巩固的话,还是有遗忘的部分,还是需要温习,这里做份笔记,记录一下

前续

先简单描述下碰到的题目,要求是写出2个print的结果

浅谈Python浅拷贝、深拷贝及引用机制

可以看到,a指向了一个列表list对象,在Python中,这样的赋值语句,其实内部含义是指a指向这个list所在内存地址,可以看作类似指针的概念。

而b,注意,他是把a对象包裹进一个list,并且乘以5,所以b的样子应该是一个大list,里面元素都是a

而当a对象进行了append操作后,其实,隐含的意思是,内存中的这个list进行了修改,所有对此对象进行引用的对象,都会发生改变

我将a的id打印出来,并且,同时打印b这个对象中所包含的元素a的id,这样可以看到,在b这个list中,每个元素的id,和a是一样的

浅谈Python浅拷贝、深拷贝及引用机制

我们可以看到,a对象的id(内存地址)为10892296,虽然b把a包裹进了新的list,但是,这个元素引用的,还是相同地址的对象,可以用下图来解释

浅谈Python浅拷贝、深拷贝及引用机制

之后,我们对a进行了append操作,由于list是一个可变对象,所以,他的内存地址并没有改变,但是,对于内存中这个地址的引用的所有对象,都会被一同改变可以从上面测试图分割线下半部分看出来.

由此,引出了对Python引用机制和浅复制及深复制的复习

Python的引用机制

引用机制案例1

由上面的例子,我们可以看到,python的引用传递,最终结果是让2个对象都引用内存中同一块区域的内容

所以我们来看一下下面的例子

B通过A,同样引用了id为17446024的地址的内容,2者的id(内存地址)都是一毛一样的

所以,通过A的操作  A[0]=3  或是   A[3].append(6)  ,都会对这块内存中的内容进行修改(因为list是可变对象,所以内存地址并不会改变,这个后面再讲)

这个是最基本的引用案例 (另外说句,由于A和B都指向了同一块内存地址,所以通过B修改的内容,也能反映到A上面去)

浅谈Python浅拷贝、深拷贝及引用机制

引用机制案例2

我们再来看一个案例

看题目貌似是会把元素2替换成本身这个列表,结果也许应该是 A=[1,[1,2,3],3]

但其实并不是!!你可以看到,红框中部分,中间有无限多个嵌套

为什么会这样呢?

其实是因为,A指向的是[1,2,3]这个列表,在这个例子中,只是把A的第2个元素,指向了A对象本身,所以说,只是A的结构发生了变化!但是,A还是指向那个对象

我们可以通过打印A的id来看,他的指向是没有变的!!

浅谈Python浅拷贝、深拷贝及引用机制

来看一下,A的指向并没有变

浅谈Python浅拷贝、深拷贝及引用机制

那如果我们要达到最后输出效果是 [1,[1,2,3],3]的效果,应该如何来操作呢?

这里,我们就要用到浅复制了,用法可以如下

 浅谈Python浅拷贝、深拷贝及引用机制

浅复制和深复制

浅复制

现在,就来说说浅复制和深复制,上面的方法实际上只是进行了浅复制,shallow copy,含义是他是对原来引用的对象进行了复制,但是不再引用同一对象地址

看下面的例子,B通过 B = A[:] 操作,来进行了浅复制,你可以看到,浅复制之后,A和B引用的内存地址已经是不同的了
但是,A和B内部的元素的引用地址,还是相同的,这点要非常注意!是有区别的!!!

A和B的引用内存地址的不同,带来的效果是,你在B上面进行的操作,并不会影响到A。

浅谈Python浅拷贝、深拷贝及引用机制

浅拷贝归纳:

所以浅拷贝,可以归纳为,复制一份引用,新的对象和原来的对象的引用被区分开,但是内部元素的地址引用还是相同的

但是浅复制也会有问题,问题在哪里呢?就是碰到有嵌套的情况,比如下面的情况可以看到,我给B赋值了一份A的浅复制,这样A和B的id(内存地址)就不一样了。

所以,当我修改A[0]=8的时候,B不会被影响到,因为他们A和B两者是独立的引用,但是这里中间有一个嵌套的列表 [4,5,6]
这个[4,5,6]我们可以理解为:A和B还共同引用着,也就是对于A和B的第二个元素来说,他们还是指向同一块内存地址的。

另外要说一句,由于int是不可变类型,所以,把A[0]修改成8之后,他的引用地址就变了!就和B[0]这个元素的引用区分开了。

浅谈Python浅拷贝、深拷贝及引用机制

深复制

那如何面对这样的情况呢?就要用到python模块里面的copy模块了

copy模块有2个功能

1: copy.copy(你要复制的对象) : 这个是浅拷贝,和前面对list进行的 [:] 操作性质是一样的

2: copy.deepcopy(你要复制的对象) : 这个是深拷贝,他除了和浅拷贝一样,会新生成一份对象的引用,另外对于内部的元素,都会新生成引用,以独立分开.

看下面的例子,当你给B赋值一份A的深复制之后,他俩可以说是完全独立开了,无论你修改的是A里面的不可变元素,还是修改A里面嵌套的可变元素,结果都不会影响到B

我的理解是:深复制可以称之为递归拷贝,他会把所有嵌套的可变元素都拷贝一下,然后独立引用出来.

浅谈Python浅拷贝、深拷贝及引用机制

深复制归纳:

深复制的效果,除了和浅复制一样,将对象的引用新生成一份引用之外,内部所有嵌套的元素,他都会帮你一一独立开.

自己画了2张图,以表示浅复制和深复制的效果区别

需要说明的是,虽然浅复制之后,列表内不可变元素的引用地址还是相同的,但是,正因为他们是不可变元素,所以,其中任意不可变元素被改变之后,引用地址都会是新的,而不会影响到原来的引用地址。

 

浅谈Python浅拷贝、深拷贝及引用机制

浅谈Python浅拷贝、深拷贝及引用机制

总结

所以,到这里,浅复制和深复制的机制,基本上理解了。

另外还有特殊情况需要说明

对于不可变类型:int, str, tuple, float 这样的元素来说,没有拷贝这个说法,他们被修改之后,引用地址就是直接改变了,如下面

浅谈Python浅拷贝、深拷贝及引用机制

但是,如果不可变类型内部有嵌套的可变类型的时候,还是可以使用深复制的

浅谈Python浅拷贝、深拷贝及引用机制

另外要提醒一句,平时我们用的最多的直接赋值(或者可以说是直接传递引用)的方法,比如下面的例子

他是将a和b两个可变元素同时指向一个内存地址,所以,任何改变都是波及到a和b的

浅谈Python浅拷贝、深拷贝及引用机制

最后

可变类型:list , set , dict

不可变类型:int, str , float , tuple

浅复制方法:[:] , copy.copy() ,  使用工厂函数(list/dir/set)

深复制方法:copy.deepcopy()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python中分数的相关使用教程
Mar 30 Python
Python获取运行目录与当前脚本目录的方法
Jun 01 Python
在Django的URLconf中使用命名组的方法
Jul 18 Python
让python 3支持mysqldb的解决方法
Feb 14 Python
Python实现解析Bit Torrent种子文件内容的方法
Aug 29 Python
PHP实现发送和接收JSON请求
Jun 07 Python
python用plt画图时,cmp设置方法
Dec 13 Python
python+openCV利用摄像头实现人员活动检测
Jun 22 Python
python3 线性回归验证方法
Jul 09 Python
python超时重新请求解决方案
Oct 21 Python
使用Bazel编译TensorBoard教程
Feb 15 Python
Python编程根据字典列表相同键的值进行合并
Oct 05 Python
利用python获取某年中每个月的第一天和最后一天
Dec 15 #Python
python中快速进行多个字符替换的方法小结
Dec 15 #Python
Python制作简易注册登录系统
Dec 15 #Python
用yum安装MySQLdb模块的步骤方法
Dec 15 #Python
Python3.6正式版新特性预览
Dec 15 #Python
再谈Python中的字符串与字符编码(推荐)
Dec 14 #Python
Python文件操作,open读写文件,追加文本内容实例
Dec 14 #Python
You might like
如何删除多级目录
2006/10/09 PHP
php中怎么搜索相关联数组键值及获取之
2013/10/17 PHP
10款PHP开源商城系统汇总介绍
2015/07/23 PHP
php实现异步数据调用的方法
2015/12/24 PHP
Laravel 5.1 on SAE环境开发教程【附项目demo源码】
2016/10/09 PHP
PHP操作XML中XPath的应用示例
2019/07/04 PHP
php常用日期时间函数实例小结
2019/07/04 PHP
php 使用html5 XHR2实现上传文件与进度显示功能示例
2020/03/03 PHP
解放web程序员的输入验证
2006/10/06 Javascript
JSON 学习之完全手册 图文
2007/05/29 Javascript
jQuery中:gt选择器用法实例
2014/12/29 Javascript
javascript中offset、client、scroll的属性总结
2015/08/13 Javascript
JavaScript高级教程5.6之基本包装类型(详细)
2015/11/23 Javascript
JS中Array数组学习总结
2017/01/18 Javascript
微信小程序中页面FOR循环和嵌套循环
2017/06/21 Javascript
快速解决bootstrap下拉菜单无法隐藏的问题
2018/08/10 Javascript
深入学习js函数的隐式参数 arguments 和 this
2019/06/24 Javascript
[43:43]完美世界DOTA2联赛PWL S2 LBZS vs Forest 第三场 11.29
2020/12/02 DOTA
Python读写及备份oracle数据库操作示例
2018/05/17 Python
Django框架使用富文本编辑器Uedit的方法分析
2018/07/31 Python
对Python的zip函数妙用,旋转矩阵详解
2018/12/13 Python
python实现手机销售管理系统
2019/03/19 Python
Python自定义函数计算给定日期是该年第几天的方法示例
2019/05/30 Python
Python解释器以及PyCharm的安装教程图文详解
2020/02/26 Python
pip安装tensorflow的坑的解决
2020/04/19 Python
Python实现冒泡排序算法的完整实例
2020/11/04 Python
jupyter使用自动补全和切换默认浏览器的方法
2020/11/18 Python
Python 远程开关机的方法
2020/11/18 Python
HTML5在手机端实现视频全屏展示方法
2020/11/23 HTML / CSS
百日安全活动总结
2014/05/04 职场文书
党的群众路线对照检查材料
2014/08/27 职场文书
医学检验专业自荐信
2014/09/18 职场文书
2014年单位法制宣传日活动总结
2014/11/01 职场文书
办公室年度工作总结2015
2015/05/21 职场文书
入党申请书怎么写?
2019/06/21 职场文书
Mysql存储过程、触发器、事件调度器使用入门指南
2022/01/22 MySQL