详解Python核心编程中的浅拷贝与深拷贝


Posted in Python onJanuary 07, 2018

一、问题引出浅拷贝

首先看下面代码的执行情况:

a = [1, 2, 3]
print('a = %s' % a) # a = [1, 2, 3]
b = a
print('b = %s' % b) # b = [1, 2, 3]
a.append(4) # 对a进行修改
print('a = %s' % a) # a = [1, 2, 3, 4]
print('b = %s' % b) # b = [1, 2, 3, 4]

b.append(5) # 对b进行修改
print('a = %s' % a) # a = [1, 2, 3, 4, 5]
print('b = %s' % b) # b = [1, 2, 3, 4, 5]

上面的代码比较简单,定义了一个变量a,它是一个数值[1, 2, 3]的列表,通过一个简单的赋值语句 b = a 定义变量b,它同样也是数值[1, 2, 3]的列表。

问题是:如果此时修改变量a,对b会有影响吗?同样如果修改变量b,对a又会有影响吗?

从代码运行结果可以看出,无论是修改b还是修改a(注意这种修改的方式,是用append,直接修改原列表,而不是重新赋值),都另一方都是有影响的。

当然这个原因其实很好理解,变量a指向的是列表[1, 2, 3]的地址值,当用 = 进行赋值运算时,b的值也相应的指向的列表[1, 2, 3]的地址值。在python中,可以通过id(变量)的方法来查看地址值,我们来查看下a,b变量的地址值,看是不是相等:

# 注意,不同机器上,这个值不同,但只要a,b两个变量的地址值是一样的就能说明问题了
print(id(a)) # 4439402312
print(id(b)) # 4439402312

所以原理如下图所示:

详解Python核心编程中的浅拷贝与深拷贝

因此,只要是在地址值:4439402312上的列表进行修改的话,a,b都会发生变化。(注意我这里说的修改,是在地址值为:4439402312上的列表进行的修改,而不说对变量a进行修改,因为对变量a的修改方式有两种,本文结尾会解释为什么不说对变量a进行修改) 。所以我们便引出了以下概念:

对于这种是将引用进行拷贝赋值给另一个变量的方式(即拷贝的是地址值),我们称之为浅拷贝。

二、如何进行深拷贝

python中实现深拷贝的方式很简单,只需要引入copy模块,调用里面的deepcopy()的方法即可,示例代码如下:

import copy
a = [1, 2, 3]
b = copy.deepcopy(a)

print('a = %s' % a) # a = [1, 2, 3]
print('b = %s' % b) # b = [1, 2, 3]

b.append(4)
print('a = %s' % a) # a = [1, 2, 3]
print('b = %s' % b) # b = [1, 2, 3, 4]

从代码执行情况来看,我们已经实现了深拷贝。这时我们再来看下两个变量的地址值:

print(id(a)) # 4321416008
print(id(b)) # 4321416200

果然就不一样了。我们再通过一个图来看下深拷贝的原理:

详解Python核心编程中的浅拷贝与深拷贝

三、copy模块方法简介

从深拷贝的实现过程,我们知道copy模块,也使用了里面的deepcopy()方法。下面我们来介绍下copy模块中的copy()与deepcopy()方法。

首先介绍我们已经使用过的deepcopy()方法,官方文档介绍如下:

详解Python核心编程中的浅拷贝与深拷贝

简单解释下文档中对这个方法的说明:

1. 返回值是对这个对象的深拷贝

2. 如果拷贝发生错误,会报copy.err异常

3. 存在两个问题,第一是如果出递归对象,会递归的进行拷贝,第二正因为会递归拷贝,会导致出现拷贝过多的情况

4. 关于两种拷贝方式的区别都是相对是引用对象

前两点很好理解,针对第三点,我们用代码进行解释:

import copy
a = [1, 2, 3]
b = [3, 4, 5]
c = [a, b] # 列表嵌套
d = copy.deepcopy(c)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c.append(4)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c[0].append(4) # 相当于a.append(4)
print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
# a.append(4)
# print('c = %s' % c) # a = [1, 2, 3]
# print('d = %s' % d) # b = [1, 2, 3]
print(id(c)) # 4314188040
print(id(d)) # 4314187976
print(id(c[0])) # 4314186568
print(id(d[0])) # 4314187912
print(id(a)) # 4314186568
print(id(b)) # 4314186760

根据代码,我们可以看到,当有嵌套对象,也就是文档中提到的递归对象,从结果我们可以看到,嵌套对象会进行递归的深拷贝。即如果c里有一个a,那么不仅c会深拷贝,a同样也会被深拷贝。原理如下图所求:

详解Python核心编程中的浅拷贝与深拷贝

接下来我们再来看copy()方法:

官方文档解释的很简单,它返回的就是对象的浅拷贝。但其实它会对最外层进行深拷贝,而如果有多层,第二层以后进行的就是浅拷贝了。代码示例如下:

import copy
a = [1, 2, 3]
b = [3, 4, 5]
c = [a, b] # 列表嵌套
d = copy.copy(c)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5]]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]]
c.append(4)
print('c = %s' % c) # c = [[1, 2, 3], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3], [3, 4, 5]] 没有发生变化,说明外层是深拷贝
c[0].append(4) # 相当于a.append(4)
print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 发生了变化,说明内层是浅拷贝
# a.append(4)
# print('c = %s' % c) # c = [[1, 2, 3, 4], [3, 4, 5], 4]
# print('d = %s' % d) # d = [[1, 2, 3, 4], [3, 4, 5]] 发生了变化,说明内层是浅拷贝
print(id(c)) # 4322576648
print(id(d)) # 4322576584 d和c地址不同,进一步说明外层是深拷贝
print(id(c[0])) # 4322575176
print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,进一步说明内层是浅拷贝
print(id(a)) # 4322575176
print(id(b)) # 4322575368

【注意】对于copy()方法,有特殊情况,比如元组类型,代码示例如下:

import copy
a = [1, 2, 3]
b = [3, 4, 5]
c = (a, b) # 列表改成元组
d = copy.copy(c)
print(id(c)) # 4303015752
print(id(d)) # 4303015752 d和c地址相同
print(id(c[0])) # 4322575176
print(id(d[0])) # 4322575176 c[0]和d[0]地址相同,进一步说明内层是浅拷贝

可以看到,这里哪怕是最外层,也是浅拷贝。

这里因为copy方法内部有判断,如果最外层的拷贝类型是不可变类型,则进行浅拷贝,反之则进行深拷贝。

至此,我们应该对浅拷贝的概念进行进一步加深理解:

如果对象中的所有元素,有一个是引用拷贝,则定义为是浅拷贝。(该定义不是官方定义,只是个人理解)

四、关于“修改”的一点说明

前面提到了修改变量,我认为修改是有两种方式,第一种在原对象上进行修改,第二种就是重新赋值。看如下代码:

import copy
a = [1, 2, 3]
b = a
a = [3, 4, 5]
print(a) # [3, 4, 5]
print(b) # [1, 2, 3]

同样是浅拷贝,但是发现修改a之后,b没有发生变化。

在修改的时候,我们很容易想当然的通过重新赋值的方式来修改,但其实这种修改方式是有问题的。当给a再次赋值的时候,其实是将a重新指向了另外一块地址区域,而原来的[1, 2, 3]那块地址区域是没有发生任何变化的,所以对于b来说,它指向的东西并没有改变。

这也解释了之前文档中关于deepcopy方法的一个说明,为什么只对引用对象有用,因为简单类型修改的方式就是重新赋值。简单理解就是你没办法通过简单类型的变量直接通过.来调用自身的方法,都只能重新赋值来改变,那么都会指向新的地址。

详解Python核心编程中的浅拷贝与深拷贝

以上就是本片文章关于Python核心编程中的浅拷贝与深拷贝的全部内容,大家可以把学习的心得写在下面的留言区,感谢你对三水点靠木的支持。

Python 相关文章推荐
Python中字符串的格式化方法小结
May 03 Python
Python使用struct处理二进制的实例详解
Sep 11 Python
Python求出0~100以内的所有素数
Jan 23 Python
python 获取文件下所有文件或目录os.walk()的实例
Apr 23 Python
Python模拟登录的多种方法(四种)
Jun 01 Python
python导入模块交叉引用的方法
Jan 19 Python
使用python serial 获取所有的串口名称的实例
Jul 02 Python
代码实例讲解python3的编码问题
Jul 08 Python
python 3.6.7实现端口扫描器
Sep 04 Python
Python实现栈的方法详解【基于数组和单链表两种方法】
Feb 22 Python
Python API len函数操作过程解析
Mar 05 Python
Python实现GIF动图以及视频卡通化详解
Dec 06 Python
用python实现的线程池实例代码
Jan 06 #Python
pip matplotlib报错equired packages can not be built解决
Jan 06 #Python
Python实现的朴素贝叶斯分类器示例
Jan 06 #Python
Python使用matplotlib绘制正弦和余弦曲线的方法示例
Jan 06 #Python
Python爬虫中urllib库的进阶学习
Jan 05 #Python
浅谈django model postgres的json字段编码问题
Jan 05 #Python
django admin添加数据自动记录user到表中的实现方法
Jan 05 #Python
You might like
PHP4实际应用经验篇(6)
2006/10/09 PHP
PHP脚本中include文件出错解决方法
2008/11/20 PHP
解析func_num_args与func_get_args函数的使用
2013/06/24 PHP
PHP Switch 语句之学习笔记
2013/09/21 PHP
PHP strip_tags保留多个HTML标签的方法
2016/05/22 PHP
jQuery代码优化 遍历篇
2011/11/01 Javascript
AngularJS内置指令
2015/02/04 Javascript
使用npm发布Node.JS程序包教程
2015/03/02 Javascript
jQuery检测某个元素是否存在代码分享
2015/07/09 Javascript
使用CoffeeScrip优美方式编写javascript代码
2015/10/28 Javascript
jQuery获取访问者IP地址的方法(基于新浪API与QQ查询接口)
2016/05/25 Javascript
JS树形菜单组件Bootstrap TreeView使用方法详解
2016/12/21 Javascript
Ajax 加载数据 练习代码
2017/01/05 Javascript
Angularjs 与 bower安装和使用详解
2017/05/11 Javascript
深究AngularJS如何获取input的焦点(自定义指令)
2017/06/12 Javascript
bootstrap timepicker在angular中取值并转化为时间戳
2017/06/13 Javascript
js图片上传的封装代码
2017/08/01 Javascript
Vue 应用中结合vux使用微信 jssdk的方法
2018/08/28 Javascript
nvm、nrm、npm 安装和使用详解(小结)
2019/01/17 Javascript
微信小程序MUI导航栏透明渐变功能示例(通过改变opacity实现)
2019/01/24 Javascript
vue点击页面空白处实现保存功能
2019/11/06 Javascript
javascript canvas检测小球碰撞
2020/04/17 Javascript
微信小程序实现锚点跳转
2020/11/23 Javascript
[01:13:01]2018DOTA2亚洲邀请赛 4.4 淘汰赛 TNC vs VG 第三场
2018/04/05 DOTA
python实现超简单端口转发的方法
2015/03/13 Python
Python实现将数据写入netCDF4中的方法示例
2018/08/30 Python
python执行scp命令拷贝文件及文件夹到远程主机的目录方法
2019/07/08 Python
印度首选时尚目的地:Reliance Trends
2018/01/17 全球购物
新加坡第一大健康与美容零售商:屈臣氏新加坡(Watsons Singapore)
2020/12/11 全球购物
大专计算机个人求职的自我评价
2013/10/21 职场文书
人事任命书怎么写
2014/06/05 职场文书
平面设计专业求职信
2014/08/09 职场文书
出纳试用期自我鉴定范文
2014/09/16 职场文书
捐款感谢信
2015/01/20 职场文书
2015年九一八事变纪念活动实施方案
2015/05/06 职场文书
vue里使用create, mounted调用方法
2022/04/26 Vue.js