详解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实现图书超期提醒
Aug 02 Python
Python实现的人工神经网络算法示例【基于反向传播算法】
Nov 11 Python
python web.py开发httpserver解决跨域问题实例解析
Feb 12 Python
Python实现检测文件MD5值的方法示例
Apr 11 Python
使用实现XlsxWriter创建Excel文件并编辑
May 04 Python
pandas中遍历dataframe的每一个元素的实现
Oct 23 Python
用python求一重积分和二重积分的例子
Dec 06 Python
Python 过滤错误log并导出的实例
Dec 26 Python
pandas数据拼接的实现示例
Apr 16 Python
keras用auc做metrics以及早停实例
Jul 02 Python
python 实现数据库中数据添加、查询与更新的示例代码
Dec 07 Python
【超详细】八大排序算法的各项比较以及各自特点
Mar 31 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
使用php get_headers 判断URL是否有效的解决办法
2013/04/27 PHP
解析PHP中ob_start()函数的用法
2013/06/24 PHP
php取整函数ceil,floo,round的用法及介绍
2013/08/31 PHP
yii框架表单模型使用及以数组形式提交表单数据示例
2014/04/30 PHP
从零开始学YII2框架(三)扩展插件yii2-gird
2014/08/20 PHP
php多次include后导致全局变量global失效的解决方法
2015/02/28 PHP
Js 时间间隔计算的函数(间隔天数)
2011/11/15 Javascript
利用jquery包将字符串生成二维码图片
2013/09/12 Javascript
Javascript中string转date示例代码
2013/11/01 Javascript
jQuery 中国省市两级联动选择附图
2014/05/14 Javascript
js实现简单折叠、展开菜单的方法
2015/08/28 Javascript
如何解决React官方脚手架不支持Less的问题(小结)
2018/09/12 Javascript
Vue中Quill富文本编辑器的使用教程
2018/09/21 Javascript
js中Array对象的常用遍历方法详解
2019/01/17 Javascript
pm2启动ssr失败的解决方法
2019/06/29 Javascript
jquery 键盘事件 keypress() keydown() keyup()用法总结
2019/10/23 jQuery
Vue 嵌套路由使用总结(推荐)
2020/01/13 Javascript
[50:28]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 Newbee vs KG
2018/04/01 DOTA
Python cookbook(数据结构与算法)实现对不原生支持比较操作的对象排序算法示例
2018/03/15 Python
python获取微信小程序手机号并绑定遇到的坑
2018/11/19 Python
Python基础学习之基本数据结构详解【数字、字符串、列表、元组、集合、字典】
2019/06/18 Python
Python 多线程其他属性以及继承Thread类详解
2019/08/28 Python
详解如何在cmd命令窗口中搭建简单的python开发环境
2019/08/29 Python
pytorch点乘与叉乘示例讲解
2019/12/27 Python
Python打开文件、文件读写操作、with方式、文件常用函数实例分析
2020/01/07 Python
pyCharm 设置调试输出窗口中文显示方式(字符码转换)
2020/06/09 Python
Python爬虫爬取博客实现可视化过程解析
2020/06/29 Python
python中return不返回值的问题解析
2020/07/22 Python
关于Java String的一道面试题
2013/09/29 面试题
审核会计岗位职责
2013/11/08 职场文书
《小池塘》教学反思
2014/02/28 职场文书
优秀广告词大全
2014/03/19 职场文书
2014年社区教育工作总结
2014/12/02 职场文书
仓库管理员岗位职责
2015/02/03 职场文书
高中诗歌鉴赏教学反思
2016/02/16 职场文书
检讨书范文
2019/04/16 职场文书