详解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 (1)
Oct 31 Python
python 参数列表中的self 显式不等于冗余
Dec 01 Python
浅要分析Python程序与C程序的结合使用
Apr 07 Python
Python 中 Virtualenv 和 pip 的简单用法详解
Aug 18 Python
深入理解Python中的*重复运算符
Oct 28 Python
Python爬虫信息输入及页面的切换方法
May 11 Python
Python判断对象是否为文件对象(file object)的三种方法示例
Apr 26 Python
PyQt5实现简易电子词典
Jun 25 Python
python 并发编程 多路复用IO模型详解
Aug 20 Python
Python基于requests实现模拟上传文件
Apr 21 Python
Python制作简单的剪刀石头布游戏
Dec 10 Python
ASP.NET Core中的配置详解
Feb 05 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 检查扩展库或函数是否可用的代码
2010/04/06 PHP
php Smarty模板生成html文档的方法
2010/04/12 PHP
深入php self与$this的详解
2013/06/08 PHP
调整PHP的性能
2013/10/30 PHP
php 伪静态之IIS篇
2014/06/02 PHP
浅析ThinkPHP中execute和query方法的区别
2014/06/13 PHP
PHP安全的URL字符串base64编码和解码
2014/06/19 PHP
thinkphp实现like模糊查询实例
2014/10/29 PHP
thinkphp实现发送邮件密码找回功能实例
2014/12/01 PHP
joomla实现注册用户添加新字段的方法
2016/05/05 PHP
php字符串操作针对负值的判断分析
2016/07/28 PHP
修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)
2017/08/01 PHP
JavaScript获取GridView选择的行内容
2009/04/14 Javascript
Jquery工作常用实例 使用AJAX使网页进行异步更新
2011/07/26 Javascript
Javascript无阻塞加载具体方式
2013/06/28 Javascript
理解javascript中的回调函数(callback)
2014/09/02 Javascript
JavaScript 实现打印,打印预览,打印设置
2014/12/30 Javascript
JavaScript文档碎片操作实例分析
2015/12/12 Javascript
Vue.js 2.0和Cordova开发webApp环境搭建方法
2018/02/26 Javascript
微信小程序实现红包功能(后端PHP实现逻辑)
2018/07/11 Javascript
Angular7.2.7路由使用初体验
2019/03/01 Javascript
vue实现lodop打印功能的示例
2020/11/11 Javascript
python之Socket网络编程详解
2016/09/29 Python
Python处理XML格式数据的方法详解
2017/03/21 Python
python爬取各类文档方法归类汇总
2018/03/22 Python
Python使用pyautocad+openpyxl处理cad文件示例
2019/07/11 Python
Django实现celery定时任务过程解析
2020/04/21 Python
Linux系统下升级pip的完整步骤
2021/01/31 Python
巴西宠物店在线:Geração Pet
2017/05/31 全球购物
智能家居、吸尘器、滑板车、电动自行车网上购物:Geekmaxi
2021/01/18 全球购物
Servlet的生命周期
2013/08/25 面试题
社区矫正工作方案
2014/06/04 职场文书
部队反四风对照检查材料
2014/09/26 职场文书
Python连接Postgres/Mysql/Mongo数据库基本操作大全
2021/06/29 Python
源码分析Redis中 set 和 sorted set 的使用方法
2022/03/22 Redis
Python FuzzyWuzzy实现模糊匹配
2022/04/28 Python