11

Python的深浅拷贝讲解!

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzIyNjM2MzQyNg%3D%3D&%3Bmid=2247543107&%3Bidx=1&%3Bsn=8dc48f7c167459939158a1a58c7e2902
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

↑↑↑关注后" 星标 "Datawhale

每日干货 &  每月组队学习 ,不错过

Datawhale干货 

作者:皮钱超,厦门大学,Datawhale原创作者

本文 约3000字 ,建议 阅读9分钟

审稿人:耿远昊,Datawhale成员,华东师范大学,开源教程《Joyful-Pandas》核心贡献者。

前言

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

  • 对象、数据类型、引用

  • 赋值

  • 浅拷贝

  • 深拷贝

640?wx_fmt=jpeg

一、Python对象

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

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

  • 身份

  • 类型

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

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

Python中,万物皆对象

二、数据类型

2.1 可变和不可变类型

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

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

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

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg640?wx_fmt=jpeg640?wx_fmt=jpeg

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

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

640?wx_fmt=jpeg640?wx_fmt=jpeg640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

2.2 引用

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

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

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

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

640?wx_fmt=jpeg640?wx_fmt=jpeg

三、赋值

3.1 相同数据,不同变量名

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

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg640?wx_fmt=jpeg

3.2 一个变量多次赋值

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

640?wx_fmt=jpeg640?wx_fmt=jpeg

3.3 变量赋值

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

640?wx_fmt=jpeg640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

3.4 嵌套赋值

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

640?wx_fmt=jpeg

原始数据信息:

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

四、浅拷贝

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

import copy

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

4.1 不可变类型的浅拷贝

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

640?wx_fmt=jpeg640?wx_fmt=jpeg

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

640?wx_fmt=jpegimage-20201115225938833

4.2 可变类型的浅拷贝

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

640?wx_fmt=jpegimage-20201115232303901

从上面的例子看出来:

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

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

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

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

五、深拷贝

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

5.1 不可变类型的深拷贝

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

640?wx_fmt=jpeg640?wx_fmt=jpeg640?wx_fmt=jpeg

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

5.2 可变类型的深拷贝

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

针对列表数据:

640?wx_fmt=jpeg640?wx_fmt=jpeg

针对字典数据:

640?wx_fmt=jpeg640?wx_fmt=jpeg

我们可以得出结论:

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

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

640?wx_fmt=jpeg

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

六、元组的深浅拷贝

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

6.1 不存在嵌套结构

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

640?wx_fmt=jpeg

6.2 存在嵌套结构

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

640?wx_fmt=jpeg

七、is和==

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

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

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

640?wx_fmt=jpeg640?wx_fmt=jpeg

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

640?wx_fmt=jpeg640?wx_fmt=jpeg

总结

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

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

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

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

640?wx_fmt=png

“整理不易, 三连


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK