Python-copy()与deepcopy()区别详解


Posted in Python onJuly 12, 2019

最近在实习,boss给布置了一个python的小任务,学习过程中发现copy()和deepcopy()这对好基友实在是有点过分,搞的博主就有点傻傻分不清啊,但是呢本着一探到底的精神,还是要查资料搞清楚这对好基友的区别。

其实呢,copy()与deepcopy()之间的区分必须要涉及到python对于数据的存储方式。

首先直接上结论:

—?我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。

—?而浅复制并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。这就和我们寻常意义上的复制有所不同了。

对于简单的 object,用 shallow copy 和 deep copy 没区别

复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。

看不懂文字没关系我们来看代码:

>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 里边有三个元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False 
#cop1 和 cop2 看上去相同,但已不再是同一个object
>>> origin[2][0] = "hey!" 
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2

可以看到 cop1,也就是 shallow copy 跟着 origin 改变了。而 cop2 ,也就是 deep copy 并没有变。

似乎 deep copy 更加符合我们对「复制」的直觉定义: 一旦复制出来了,就应该是独立的了。如果我们想要的是一个字面意义的「copy」,那就直接用 deep_copy 即可。

那么为什么会有 shallow copy 这样的「假」 copy 存在呢? 这就是有意思的地方了。

python的数据存储方式

Python 存储变量的方法跟其他 OOP 语言不同。它与其说是把值赋给变量,不如说是给变量建立了一个到具体值的 reference。

当在 Python 中 a = something 应该理解为给 something 贴上了一个标签 a。当再赋值给 a 的时候,就好象把 a 这个标签从原来的 something 上拿下来,贴到其他对象上,建立新的 reference。 这就解释了一些 Python 中可能遇到的诡异情况:

>> a = [1, 2, 3]
>>> b = a
>>> a = [4, 5, 6] //赋新的值给 a
>>> a
[4, 5, 6]
>>> b
[1, 2, 3]
# a 的值改变后,b 并没有随着 a 变

>>> a = [1, 2, 3]
>>> b = a
>>> a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素
>>> a
[4, 5, 6]
>>> b
[4, 5, 6]
# a 的值改变后,b 随着 a 变了

上面两段代码中,a 的值都发生了变化。区别在于,第一段代码中是直接赋给了 a 新的值(从 [1, 2, 3] 变为 [4, 5, 6]);而第二段则是把 list 中每个元素分别改变。

而对 b 的影响则是不同的,一个没有让 b 的值发生改变,另一个变了。怎么用上边的道理来解释这个诡异的不同呢?

首次把 [1, 2, 3] 看成一个物品。a = [1, 2, 3] 就相当于给这个物品上贴上 a 这个标签。而 b = a 就是给这个物品又贴上了一个 b 的标签。 

第一种情况:

a = [4, 5, 6] 就相当于把 a 标签从 [1 ,2, 3] 上撕下来,贴到了 [4, 5, 6] 上。

在这个过程中,[1, 2, 3] 这个物品并没有消失。 b 自始至终都好好的贴在 [1, 2, 3] 上,既然这个 reference 也没有改变过。 b 的值自然不变。

第二种情况:

a[0], a[1], a[2] = 4, 5, 6 则是直接改变了 [1, 2, 3] 这个物品本身。把它内部的每一部分都重新改装了一下。内部改装完毕后,[1, 2, 3] 本身变成了 [4, 5, 6]。

而在此过程当中,a 和 b 都没有动,他们还贴在那个物品上。因此自然 a b 的值都变成了 [4, 5, 6]。

搞明白这个之后就要问了,对于一个复杂对象的浅copy,在copy的时候到底发生了什么?
再看一段代码:

>>> import copy
>>> origin = [1, 2, [3, 4]]
#origin 里边有三个元素:1, 2,[3, 4]
>>> cop1 = copy.copy(origin)
>>> cop2 = copy.deepcopy(origin)
>>> cop1 == cop2
True
>>> cop1 is cop2
False 
#cop1 和 cop2 看上去相同,但已不再是同一个object
>>> origin[2][0] = "hey!" 
>>> origin
[1, 2, ['hey!', 4]]
>>> cop1
[1, 2, ['hey!', 4]]
>>> cop2
[1, 2, [3, 4]]
#把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2

学过docker的人应该对镜像这个概念不陌生,我们可以把镜像的概念套用在copy上面。 

copy对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列,字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了。

所以说看这里的origin[2],也就是 [3, 4] 这个 list。根据 shallow copy 的定义,在 cop1[2] 指向的是同一个 list [3, 4]。那么,如果这里我们改变了这个 list,就会导致 origin 和 cop1 同时改变。这就是为什么上边 origin[2][0] = “hey!” 之后,cop1 也随之变成了 [1, 2, [‘hey!', 4]]。

deepcopy的时候会将复杂对象的每一层复制一个单独的个体出来。

这时候的 origin[2] 和 cop2[2] 虽然值都等于 [3, 4],但已经不是同一个 list了。即我们寻常意义上的复制。

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

Python 相关文章推荐
Using Django with GAE Python 后台抓取多个网站的页面全文
Feb 17 Python
详解Python如何获取列表(List)的中位数
Aug 12 Python
遍历python字典几种方法总结(推荐)
Sep 11 Python
Python标准库06之子进程 (subprocess包) 详解
Dec 07 Python
python3+mysql查询数据并通过邮件群发excel附件
Feb 24 Python
python3+PyQt5使用数据库表视图
Apr 24 Python
用Python获取摄像头并实时控制人脸的实现示例
Jul 11 Python
Python Sympy计算梯度、散度和旋度的实例
Dec 06 Python
selenium+python实现自动登陆QQ邮箱并发送邮件功能
Dec 13 Python
在Pytorch中使用Mask R-CNN进行实例分割操作
Jun 24 Python
对python中list的五种查找方法说明
Jul 13 Python
如何用python实现一个HTTP连接池
Jan 14 Python
新手入门Python编程的8个实用建议
Jul 12 #Python
python读取并写入mat文件的方法
Jul 12 #Python
numpy数组广播的机制
Jul 12 #Python
Python的numpy库下的几个小函数的用法(小结)
Jul 12 #Python
python读取.mat文件的数据及实例代码
Jul 12 #Python
如何用Python破解wifi密码过程详解
Jul 12 #Python
python pip源配置,pip配置文件存放位置的方法
Jul 12 #Python
You might like
JoshChen_web格式编码UTF8-无BOM的小细节分析
2013/08/16 PHP
PHP实现清除wordpress里恶意代码
2015/10/21 PHP
php写入txt乱码的解决方法
2019/09/17 PHP
用Javascript数组处理多个字符串的连接问题
2009/08/20 Javascript
动态加载外部javascript文件的函数代码分享
2011/07/28 Javascript
Javascript面向对象扩展库代码分享
2012/03/27 Javascript
JavaScript动态操作表格实例(添加,删除行,列及单元格)
2013/11/25 Javascript
浏览器缩放检测的js代码
2014/09/28 Javascript
使用命令对象代替switch语句的写法示例
2015/02/28 Javascript
使用impress.js制作幻灯片
2015/09/09 Javascript
jQuery实现的简单分页示例
2016/06/01 Javascript
总结十个Angular.js由浅入深的面试问题
2016/08/26 Javascript
jquery mobile移动端幻灯片滑动切换效果
2020/04/15 Javascript
bootstrap PrintThis打印插件使用详解
2017/02/20 Javascript
react在安卓中输入框被手机键盘遮挡问题的解决方法
2018/09/03 Javascript
angular 未登录状态拦截路由跳转的方法
2018/10/09 Javascript
nodejs aes 加解密实例
2018/10/10 NodeJs
详解webpack打包后如何调试的方法步骤
2018/11/07 Javascript
vue 强制组件重新渲染(重置)的两种方案
2019/10/29 Javascript
[01:08:24]DOTA2-DPC中国联赛 正赛 RNG vs Phoenix BO3 第一场 2月5日
2021/03/11 DOTA
跟老齐学Python之赋值,简单也不简单
2014/09/24 Python
python返回昨天日期的方法
2015/05/13 Python
Python使用functools实现注解同步方法
2018/02/06 Python
python读取TXT每行,并存到LIST中的方法
2018/10/26 Python
Django跨域资源共享问题(推荐)
2020/03/09 Python
Xadmin+rules实现多选行权限方式(级联效果)
2020/04/07 Python
Pyside2中嵌入Matplotlib的绘图的实现
2021/02/22 Python
阿里巴巴国际站:Alibaba.com
2016/07/21 全球购物
一套Delphi的笔试题二
2013/05/11 面试题
119消防日活动总结
2014/08/29 职场文书
学校2014重阳节活动策划方案
2014/09/16 职场文书
优秀教师个人总结
2015/02/11 职场文书
创业项目(超低成本创业项目)
2019/08/16 职场文书
古诗文之爱国名句(77句)
2019/09/24 职场文书
Java中PriorityQueue实现最小堆和最大堆的用法
2021/06/27 Java/Android
Python保存并浏览用户的历史记录
2022/04/29 Python