【Python基础】Python的深浅拷贝讲解

news/2024/7/5 7:58:37

点击上方“小白学视觉”,选择加"星标"或“置顶”

重磅干货,第一时间送达dc9355df3c037ad7a4fd0c036a11da25.png

前言

在很多语言中都存在深浅拷贝两种拷贝数据的方式,Python中也不例外。本文中详细介绍了Python中的深浅拷贝的相关知识,文章的内容包含:

  • 对象、数据类型、引用

  • 赋值

  • 浅拷贝

  • 深拷贝

ea67a34ae060f770dfc27f35ea7bd2c9.png

一、Python对象

我们经常听到:在Python中一切皆对象。其实,说的就是我们在Python中构造的任何数据类型都是一个对象,不管是数字、字符串、字典等常见的数据结构,还是函数,甚至是我们导入的模块等,Python都会把它当做是一个对象来处理。

所有的Python对象都拥有3个属性:

  • 身份

  • 类型

我们看一个简单的例子来理解上面的3个属性:

假设我们声明了一个name变量,通过id、type方法能够查看对象的身份和类型:

71abe484e20aa12a0260eea7fdc0bb8d.png

甚至是type本身也是一个对象,它也拥有自己的身份、类型:

0cbce921312a268b0c68e3340ee44233.png

Python中,万物皆对象

二、数据类型

2.1 可变和不可变类型

在Python中,按照更新对象的方式,我们可以将对象分为2大类:可变数据类型不可变数据类型

  • 不可变数据类型:数值、字符串、布尔值。不可变对象就是对象的身份和值都不可变。新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。

  • 可变数据类型:列表、字典、集合。所谓的可变指的是可变对象的值可变,但是身份是不可变的。

首先我们看看不可变对象:

1b67bae9bb99aa23109104cad3721177.png

当我们定义了一个对象str1,给其赋值了“python”,便会在内存中找到一个固定的内存地址来存放;但是,当我们将“python”定义成另一个变量名的时候,我们发现:它在内存中的位置是不变的

c34013281e92e5f937605922afcb1654.png

也就是说,这个变量在计算机内存中的位置是不变的,只是换了一个名字来存放,来看3个实际的例子:

2f540005df68ae9374a20e1ca103e671.pngdcdb8c2dd0f78018c2fcf6667182bd65.pngb3acfce6151607be392f207a17117c8a.png

以上的例子说明:当我们对字符串、数值型、布尔值的数据改变变量名,并不会影响到数据在内存中的位置。

我们看看可变类型的例子,列表、字典、集合都是一样的效果:

4fddf2d831c8ec7cbb5248513578ab56.png13403dc886bd31e6a2c3b7859ff48065.pngfd1545ae951837c8883eedb6a6b27052.png

虽然是相同的数据,但是变量名字不同,内存中仍然会开辟新的内存地址来进行存放相同的数据,我们以字典为例:

dfa01413a80e29824fd3059be4b3e095.png

2.2 引用

在Python语言中,每个对象都会在内存中申请开辟一块新的空间来保存对象;对象在内存中所在位置的地址称之为引用。

可以说,我们定义的变量名实际上就是对象的地址引用。引用实际上就是内存中的一个数字地址编号。在使用对象的时候,只要知道这个对象的地址,我们就可以操作这个对象。

因为这个数字地址不太容易记忆,所以我们使用变量名的形式来代替对象的数字地址。在Python中,变量就是地址的一种表示形式,并不会开辟新的存储空间。

我们通过一个例子来说明变量和变量指向的引用(内存地址)实际上就是一个东西:

4ad699580518a45361eeea5097202712.png8eb28da0b6a315aad23e527d15aaa8ca.png

三、赋值

3.1 相同数据,不同变量名

讨论完Python的对象、属性和引用3个重要的概念之后,在正式介绍深浅拷贝之前,我们先讨论Python中的赋值

在Python中,每次赋值都会开辟新的内存地址来存放数据,比如我们同时存放一个列表[1,2,3],即使数据是相同的,但是内存地址却不同:

5434b975d7cb004d3fd51af9dafabcfe.png

其实就是两个不同的变量,只是恰好它们存放了相同的数据而已,但是存放的地址是不同的。

982b0babae7378d52458c8281e5aefaf.png

我们给v1列表追加了一个元素,发现它的内存地址是不变的,当然v2肯定是不变的:

c09ffe8066327bd60835f50dcc02c139.png2a10d90ec77a10d049b3d520c6488e4f.png

3.2 一个变量多次赋值

如果我们对一个变量多次赋值,其内存是会变化的:

23796c23d79c53816237029adf067098.png74ca9e23e499e04e13e8b6a020e01f46.png

3.3 变量赋值

将一个变量赋值给另一个变量,其实它们就是同一个对象:数据相同,在内存中的地址也相同:

57a2a83ecc2f2e8d133a8a040f791474.pngc47cea96f662bf51b40204d609c1cea6.png

当我们给V1追加一个元素,V2也会同时变化:

4a88edaa8e6a18de5eaf25536036beec.png

实际上它们就是同一个对象!!!!

3.4 嵌套赋值

如果是列表中嵌套着另外的列表,那么当改变其中一个列表的时候,另一个列表中的也会随着改变:

7d48db2adeb076696d2508ba41dfc531.png

原始数据信息:

133323142f72691ce1e3e1f39389d341.png

当我们给v1追加了新元素之后:

256c37e25a04b4723d5512bdc8cd05c8.png

总结:赋值其实就是将一个对象的地址赋值给一个变量,使得变量指向该内存地址。

四、浅拷贝

在Python中进行拷贝之前,我们需要导入模块:

import copy

⚠️浅拷贝只是拷贝数据的第一层,不会拷贝子对象

4.1 不可变类型的浅拷贝

如果只是针对不可变的数据类型(字符串、数值型、布尔值),浅拷贝的对象和原数据对象是相同的内存地址:

653e168cce8fb33b32649dbc5f68efa2.png9f56ceb4337a435e921a4ac885455aff.png

从上面的结果中我们可以看出来:针对不可变类型的浅拷贝,只是换了一个名字,对象在内存中的地址其实是不变的

8ea9e92d257d5661e4743ad09f6c786f.png
image-20201115225938833

4.2 可变类型的浅拷贝

首先我们讨论的是不存在嵌套类型的可变类型数据(列表、字典、集合):

34e75470791373b78ec9c3e7e5011259.png
image-20201115232303901

从上面的例子看出来:

  • 列表本身的浅拷贝对象的地址和原对象的地址是不同的,因为列表是可变数据类型。

  • 列表中的元素(第1个元素为例)和浅拷贝对象中的第一个元素的地址是相同的,因为元素本身是数值型,是不可变的

通过一个图形来说明这个关系:

c53d3369b7805fd14de0edef41725482.png

字典中也存在相同的情况:字典本身的内存地址不同,但是里面的键、值的内存地址是相同的,因为键值都是不可变类型的数据。

38014cbcc71c849646c8271931106b80.png

如果可变类型的数据中存在嵌套的结构:

fb4890a8f3c12fcb9e7a45095a9354c5.png

从上面的两个例子中我们可以看出来:

在可变类型的数据中,如果存在嵌套的结构类型,浅拷贝只复制最外层的数据,导致内存地址发生变化,里面数据的内存地址不会变

五、深拷贝

深拷贝不同于浅拷贝的是:深拷贝会拷贝所有的可变数据类型,包含嵌套的数据中的可变数据。深拷贝是变量对应的值复制到新的内存地址中,而不是复制数据对应的内存地址。

5.1 不可变类型的深拷贝

关于不可变类型的深浅拷贝,其效果是相同的,具体看下面的例子:

fa13292f970b4cd3d64a58569be89ef2.png9fbcec561da39909a2c18a2567b4d661.png43c7d42f96bf6bf71f38c19cf1a18f72.png

我们得出一个结论:针对不可变数据类型的深浅拷贝,其结果是相同的。

5.2 可变类型的深拷贝

首先我们讨论的是不存在嵌套的情况:

针对列表数据

5511c7d927d0abba6b2873e5c030ff1d.png20017e4204a758c15cbe5925a7e7b64b.png

针对字典数据

09af8d5f4f6a2c5f62c8addf38a18225.pngfa4e6664dce591b53a64b5171b994b0e.png

我们可以得出结论:

  • 深拷贝对最外层数据是只拷贝数据,会开辟新的内存地址来存放数据。

  • 深拷贝对里面的不可变数据类型直接复制数据和地址,和可变类型的浅拷贝是相同的效果。

31a66237100266a227fd32edc2b99355.png

我们讨论存在嵌套类型的深拷贝(以列表为例)。

d43c5251f116d73052d007bfcbad3666.png

结论1:对整个存在嵌套类型的数据进行深浅拷贝都会发生内存的变化,因为数据本身是可变的。

08f14683a00428f064481856874b776b.png

结论2:我们查看第一个元素1的内存地址,发生三者是相同的,因为1是属于数值型,是不可变类型。

10704c0326756a615bd89dcaab3ad1f7.png

结论3:我们查看第三个元素即里面嵌套列表的内存,发现只有深拷贝是不同的,因为这个嵌套的列表是可变数据类型,深拷贝在拷贝了最外层之后还会继续拷贝子层级的可变类型。

30632f94cb61efbd4d925415a739410f.png

结论4:我们查看嵌套列表中的元素的内存地址,发现它们是相同的,因为元素是数值型,是不可变的,不受拷贝的影响。

六、元组的深浅拷贝

元组本身是不可变数据类型,但是其中的值是可以改变的,内部可以有嵌套可变数据类型,比如列表等,会对它的拷贝结果造成影响。

6.1 不存在嵌套结构

当元组中不存在嵌套结构的时候,元组的深浅拷贝是相同的效果:

aa0d0871fd71a5883e7783e5659256ff.png

6.2 存在嵌套结构

当元组的数据中存在嵌套的可变类型,比如列表等,深拷贝会重新开辟地址,将元组重新成成一份

ff4c000acee93bee690248972e2289c0.png

七、is和==

在文章的开始就已经谈过:在Python中每个变量都有自己的标识、类型和值。每个对象一旦创建,它的标识就绝对不会变。一个对象的标识,我们可以理解成其在内存中的地址。is()运算符比较的是两个对象的标识;id()方法返回的就是对象标识的整数表示。

总结:is()比较对象的标识;==运算符比较两个对象的值(对象中保存的数据)。在实际的编程中,我们更多关注的是值,而不是标识本身。

第一个例子我们创建了两个不同的对象,只是它们的值刚好相同而已

3a293a745c8d93aca75ee5ca7c86cdc7.pngbe9db3670b7bdccc43a4498734d293ab.png

第二个例子我们先创建了一个对象v3,然后将他赋值给另一个对象v4,其实它们就是相同的对象,所以标识(内存地址)是相同的,只是它们的名字不同而已

3613f90fd1f5820156b61c7ff2a7ee95.pngfa8d0c0157efcbccc1cab3a21602d911.png

总结

通过大量的例子,我们得出结论:

  • 在不可变数据类型中,深浅拷贝都不会开辟新的内存空间,用的都是同一个内存地址。

  • 在存在嵌套可变类型的数据时,深浅拷贝都会开辟新的一块内存空间;同时,不可变类型的值还是指向原来的值的地址。

不同的是:在嵌套可变类型中,浅拷贝只会拷贝最外层的数据,而深拷贝会拷贝所有层级可变类型数据

下载1:OpenCV-Contrib扩展模块中文版教程在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。交流群欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

http://lihuaxi.xjx100.cn/news/272476.html

相关文章

选型必看:RabbitMQ 七战 Kafka,差异立现

点击上方“方志朋”,选择“设为星标”回复”666“获取新整理的面试文章作为一个有丰富经验的微服务系统架构师,经常有人问我,“应该选择RabbitMQ还是Kafka?”。基于某些原因, 许多开发者会把这两种技术当做等价的来看待…

20分钟+1080显卡,能跑多复杂的模型?

点击上方“视学算法”,选择加"星标"或“置顶”重磅干货,第一时间送达贾浩楠 发自 凹非寺 量子位 报道 | 公众号 QbitAI20分钟生成复杂的艺术作品,而且还是用英伟达上上代的1080显卡?现在神经网络上手门槛这么亲民了吗&a…

综述:持续感知系统在边缘计算的应用

作者 | 李桂宏、乔飞来源 | 《微纳电子与智能制造》随着边缘计算技术的兴起,各种各样的感知系统给人类带来了便捷高效的生活。以日常使用的手机为例,工程师为其置入了各种各样的传感器,并通过运行其上的机器学习算法,部署了很多便…

WinForm 实现验证码

private void CheckIdentifyingCode() { Random r new Random(); string str ""; for (int i 0; i < 5; i) { int a r.Next(0, 10); str a;//将数字连接到一块 } Bitmap bm new Bitmap(150, 90);//创建位图对象 Graphics g Graphics.FromImage(bm);//在bm中…

今日宇宙最热科技:人工智能可预测死亡时间,马斯克拿下美空军1.3亿合同!...

今日全宇宙最热科技有&#xff1a;SpaceX拿下1.3亿美空军合同&#xff0c;马斯克又双叒叕要搞事情&#xff01;人工智能可以预测死亡时间了&#xff0c;就问你怕不怕&#xff01;英特尔CEO因曾与员工恋爱被调查发现而主动辞职&#xff0c;自由恋爱也是很不容易&#xff01;阿里…

5行代码,快速实现图像分割,代码逐行详解,手把手教你处理图像 | 开源

点击上方“小白学视觉”&#xff0c;选择加"星标"或“置顶”重磅干货&#xff0c;第一时间送达图像分割&#xff0c;作为计算机视觉的基础&#xff0c;是图像理解的重要组成部分&#xff0c;也是图像处理的难点之一。那么&#xff0c;如何优雅且体面的图像分割&#…

Python人工智能完整学习路线

Python 是人工智能&#xff08;机器学习&#xff09;的首选编程语言&#xff0c;它拥有众多模块&#xff0c;能完成人工智能开发的所有环节&#xff0c;没有任何一种语言使用起来如此顺手。 Python 人工智能的学习路线如下所示&#xff1a; 爬虫 要学用 Python 如何爬取数据…

java项目_JNPF快速开发平台-简单快速高效开发java项目

◆JNPF快速开发平台JNPF快速开发平台采用前后端分离技术、采用B/S架构开发&#xff0c;形成一站式开发多端&#xff08;APPPC&#xff09;使用。使用JNPF开发平台可以简单、快速、高效的构建各种类型java项目。◆JAVA版介绍JNPF.java版采用前后端分离&#xff0c;可将代码直接导…