天啦噜!仅仅5张图,彻底搞懂Python中的深浅拷贝
Python中的深浅拷贝
在讲深浅拷贝之前,我们先重温一下 is
和==
的区别。
在判断对象是否相等比较的时候我们可以用is
和 ==
- is:比较两个对象的引用是否相同,即 它们的id 是否一样
- == : 比较两个对象的值是否相同。
id() ,是Python的一个内置函数,返回对象的唯一标识,用于获取对象的内存地址。
如下
首先,会为整数1分配一个内存空间。 变量a 和 b 都指向了这个内存空间(内存地址相等),所以他们的id相等。
即 a is b
为 True
但是,真的所有整数数字都这样吗? 答案是:不是! 只有在 -25 ~ 256范围中的整数才不会重新分配内存空间。
如下所示:
因为257 超出了范围,所以id不相同,所以a is b
返回的值为False。
>>> a = 257
>>> b = 257
>>> print(id(a))
20004752
>>> print(id(b))
20001312
>>> print(a is b)
False
>>> print(a == b)
True
这样做是考虑到性能,Python对-5 到 256 的整数维护了一个数组,相当于一个缓存, 当数值在这个范围内,直接就从数组中返回相对应的引用地址了。如果不在这个范围内,会重新开辟一个新的内存空间。
is 和 == 哪个效率高?
相比之下,is
比较的效率更高,因为它只需要判断两个对象的id是否相同即可。
而==
则需要重载__eq__ 这个函数,遍历变量中的所有元素内容,逐次比较是否相同。因此效率较低
浅拷贝 深拷贝
给变量进行赋值,有两种方法 直接赋值,拷贝
直接赋值就 =
就可以了。而拷贝又分为浅拷贝和深拷贝
先说结论吧:
- 浅拷贝:拷贝的是对象的引用,如果原对象改变,相应的拷贝对象也会发生改变
- 深拷贝:拷贝对象中的每个元素,拷贝对象和原有对象不在有关系,两个是独立的对象
光看上面的概念,对新手来讲可能不太好理解。来看下面的例子吧
赋值
a = [1, 2, 3]
b = a
print(id(a)) # 52531048
print(id(b)) # 52531048
定义变量a,同时将a赋值给b。打印之后发现他们的id
是相同的。说明指向了同一个内存地址。
然后修改a的值,再查看他们的id
a = [1, 2, 3]
b = a
print(id(a)) # 46169960
a[1] = 0
print(a, b) # [1, 0, 3] [1, 0, 3]
print(id(a)) # 46169960
print(id(b)) # 46169960
这时候发现修改后的a和b以及最开始的a的内存地址是一样的。也就是说a和b还是指向了那一块内存,只不过内存里面的[1, 2, 3] 变成了[1, 0, 3]
因为每次重新执行的时候内存地址都是发生改变的,此时的id(a) 的值46169960与52531048是一样的
所以我们就可以判断出,b和a的引用是相同的,当a发生改变的时候,b也会发生改变。
赋值就是:你a无论怎么变,你指向谁,我b就跟着你指向谁。
拷贝
提到拷贝就避免不了可变对象和不可变对象。
可变对象:当有需要改变对象内部的值的时候,这个对象的id不发生变化。
不可变对象:当有需要改变对象内部的值的时候,这个对象的id会发生变化。
a = [1, 2, 3]
print(id(a)) # 56082504
a.append(4)
# 修改列表a之后 id没发生改变,可变对象
print(id(a)) # 56082504
a = 'hello'
print(id(a)) # 59817760
a = a + ' world'
print(id(a)) # 57880072
# 修改字符串a之后,id发生了变化。不可变对象
print(a) # hello world
浅拷贝
拷贝的是不可变对象,一定程度上来讲等同于赋值操作。但是对于多层嵌套结构,浅拷贝只拷贝父对象,不拷贝内部的子对象。
使用copy
模块的 copy.copy 进行浅拷贝。
import copy
a = [1, 2, 3]
b = copy.copy(a)
print(id(a)) # 55755880
print(id(b)) # 55737992
a[1] = 0
print(a, b) # [1, 0, 3] [1, 2, 3]
通俗的讲,我将现在的a 复制一份重新分配了一个内存空间。后面你a怎么改变,那跟我b是没有任何关系的。
对于列表的浅拷贝还可以通过list(), list[:] 来实现
但是!我前面提到了对于多层嵌套的结构,需要注意
看下面的例子
import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
print(id(a)) # 23967528
print(id(b)) # 21738984
# 改变a中的子列表
a[-1].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(b) # [1, 2, [3, 4, 5]] ?? 为什么不是[1, 2, [3, 4]]呢?
b是由a浅拷贝得到的。我修改了a中嵌套的列表,发现b也跟着修改了?
如果还是不太理解,可以参考下图。LIST就是一个嵌套的子对象,指向了另外一个内存空间。所以浅拷贝只是拷贝了元素1
, 2
和子对象的引用!
另外一种情况,如果嵌套的是一个元组呢?
import copy
a = [1, 2, (3, 4)]
b = copy.copy(a)
# 改变a中的元组
a[-1] += (5,)
print(a) # [1, 2, (3, 4, 5)]
print(b) # [1, 2, (3, 4)]
我们发现浅拷贝得来的b并没有发生改变。因为元组是不可变对象。改变了元组就会生成新的对象。b中的元组引用还是指向了旧的元组。
深拷贝
所谓深拷贝呢,就是重新分配一个内存空间(新对象),将原对象中的所有元素通过递归的方式进行拷贝到新对象中。
在Python中 通过copy.deepcopy()
来实现深拷贝。
import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
print(id(a)) # 66587176
print(id(b)) # 66587688
# 改变a中的可变对象
a[-1].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(b) # [1, 2, [3, 4]] 深拷贝之后字列表不会受原来的影响
结语
1、深浅拷贝都会对源对象进行复制,占用不同的内存空间
2、如果源对象没有子目录,则浅拷贝只能拷贝父目录,改动子目录时会影响浅拷贝的对象
3、列表的切片本质就是浅拷贝
史上最全Python资料汇总(长期更新)。隔壁小孩都馋哭了 --- 点击领取
天啦噜!仅仅5张图,彻底搞懂Python中的深浅拷贝的更多相关文章
- 一张图彻底搞懂JavaScript的==运算
一张图彻底搞懂JavaScript的==运算 来源 https://zhuanlan.zhihu.com/p/21650547 PS:最后,把图改了一下,仅供娱乐 : ) 大家知道,==是JavaSc ...
- 两张图彻底搞懂MyBatis的Mapper原理!
作者:肥朝 简单使用 这是一个简单的Mybatis保存对象的例子 1@Test 2public void testSave() throws Exception { 3 //创建sessionFact ...
- 一张图轻松搞懂javascript event对象的clientX,offsetX,screenX,pageX区别
总是会被javascript的event对象的clientX,offsetX,screenX,pageX 弄得头晕,于是决定做个图来区分一下(画得我手那个酸呀....) 先总结下区别: event.c ...
- 一张图彻底搞懂Spring循环依赖
1 什么是循环依赖? 如下图所示: BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类.这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖.同理,再如下图的情况: 上图中 ...
- 高频面试题:一张图彻底搞懂Spring循环依赖
1 什么是循环依赖? 如下图所示: BeanA类依赖了BeanB类,同时BeanB类又依赖了BeanA类.这种依赖关系形成了一个闭环,我们把这种依赖关系就称之为循环依赖.同理,再如下图的情况: 上图中 ...
- 一张图让你懂Python安装第三方库
- 一文搞懂Python中的所有数组数据类型
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- 让你彻底搞懂JS中复杂运算符==
让你彻底搞懂JS中复杂运算符== 大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容易让人犯错,从而成为JavaScript中“最糟糕的特性”之一. 在仔细阅读了ECMA ...
- 帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞 ...
随机推荐
- RHSA-2017:1931-中危: bash 安全和BUG修复更新(代码执行)
[root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...
- mycat 1.6实现读写分离
使用mysql的root账号执行mysql>grant all privileges on *.* to mycatuser@% identified by '123456';mysql> ...
- 解读JVM虚拟机
概要点: java虚拟机概述和基本概念 堆.栈.方法区 了解虚拟机参数 垃圾回收概念和算法.及对象的分代转换 垃圾收集器 java虚拟机的原理: 所谓虚拟机,就是一台虚拟的机器.它是一款软件,用来执行 ...
- SQl编程存储过程
过程化存储 存储过程,一组为完成特定功能.经过编译后存储在数据库中的SQL语序集 灵活性:存储过程中可以进行流程控制和循环操作来完成复杂的判断和运算 一致性:通过存储过程可以使一些关联的操作一起发生, ...
- 什么是 C 和 C ++ 标准库?学编程的你应该知道这些知识!
简要介绍编写C/C ++应用程序的领域,标准库的作用以及它是如何在各种操作系统中实现的. 我已经接触C++一段时间了,一开始就让我感到疑惑的是其内部结构:我所使用的内核函数和类从何而来? 谁发明了它们 ...
- Tensorflow学习笔记No.7
tf.data与自定义训练综合实例 使用tf.data自定义猫狗数据集,并使用自定义训练实现猫狗数据集的分类. 1.使用tf.data创建自定义数据集 我们使用kaggle上的猫狗数据以及tf.dat ...
- linux(centos8):用uniq去除文本中重复的行(去重)
一,uniq命令的用途 1, 作用: 从输入文件或标准输入中找到相邻的匹配行, 并写入到输出文件或标准输出 2, 使用时通常会搭配sort使用 说明:刘宏缔的架构森林是一个专注架构的博客,地址:htt ...
- 自定义view的drawRoundRect模拟进度条
主要方法发介绍 1:drawRoundRect参数介绍 drawRoundRect(l,t,r,b,rx,ry,paint)里面的参数可以有两种: 1:前四个参数(l,t,r,,b)分别是矩形左边距离 ...
- 安卓日常开发和逆向中常用的shell命令与非shell命令
简述shell 命令与 非shell命令区别 shell命令不用先adb shell进入界面执行 非shell命令必须要 adb shell进入界面执行 基础非shell命令 1.安装app adb ...
- 实战四:Gateway网关作全局路由转发
Gateway网关的作用主要是两个:路由转发,请求过滤.此篇讲的是路由转发,下篇介绍请求过滤. 一,创建网关module,添加依赖 1,new -> module -> maven 或直接 ...