python 变量地址_推荐 :Python的深浅拷贝讲解

前言

在很多语言中都存在深浅拷贝两种拷贝数据的方式,Python中也不例外。本文中详细介绍了Python中的深浅拷贝的相关知识,文章的内容包含:
  • 对象、数据类型、引用
  • 赋值
  • 浅拷贝
  • 深拷贝
d9e68f831919da09a4862874507dec05.png

一、Python对象

我们经常听到:在Python中一切皆对象。其实,说的就是我们在Python中构造的任何数据类型都是一个对象,不管是数字、字符串、字典等常见的数据结构,还是函数,甚至是我们导入的模块等,Python都会把它当做是一个对象来处理。 所有的Python对象都拥有3个属性:
  • 身份
  • 类型
我们看一个简单的例子来理解上面的3个属性: 假设我们声明了一个name变量,通过id、type方法能够查看对象的身份和类型:
ba678e4f63a7c9c013983f3b9a6012a3.png
甚至是type本身也是一个对象,它也拥有自己的身份、类型:
1405d054df074acd90db80d3b7df6ab7.png
Python中,万物皆对象

二、数据类型

2.1 可变和不可变类型

在Python中,按照更新对象的方式,我们可以将对象分为2大类:可变数据类型和不可变数据类型。
  • 不可变数据类型:数值、字符串、布尔值。不可变对象就是对象的身份和值都不可变。新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。
  • 可变数据类型:列表、字典、集合。所谓的可变指的是可变对象的值可变,但是身份是不可变的。
首先我们看看不可变对象:
808a66e4187b1042264bd809e6893316.png
当我们定义了一个对象str1,给其赋值了“python”,便会在内存中找到一个固定的内存地址来存放;但是,当我们将“python”定义成另一个变量名的时候,我们发现:它在内存中的位置是不变的。
682e154024aa0f213a6c0d601258228f.png
也就是说,这个变量在计算机内存中的位置是不变的,只是换了一个名字来存放,来看3个实际的例子:
e6c2f3f9f493045fc04190828d9e153a.png
c853ac74425c197705137780e597a0e4.png
a297426e39b4020a89acce02fae89540.png
以上的例子说明:当我们对字符串、数值型、布尔值的数据改变变量名,并不会影响到数据在内存中的位置。 我们看看可变类型的例子,列表、字典、集合都是一样的效果:
304f01d1672091ca731b26c3ed3fb127.png
f400a6322bddeb9da03eb7d61399e060.png
5c6a28d420a9695b6116371033d83215.png
虽然是相同的数据,但是变量名字不同,内存中仍然会开辟新的内存地址来进行存放相同的数据,我们以字典为例:
52986e17d2be17e5d513a7e846fd99c8.png

2.2 引用

在Python语言中,每个对象都会在内存中申请开辟一块新的空间来保存对象;对象在内存中所在位置的地址称之为引用。 可以说,我们定义的变量名实际上就是对象的地址引用。引用实际上就是内存中的一个数字地址编号。在使用对象的时候,只要知道这个对象的地址,我们就可以操作这个对象。 因为这个数字地址不太容易记忆,所以我们使用变量名的形式来代替对象的数字地址。在Python中,变量就是地址的一种表示形式,并不会开辟新的存储空间。 我们通过一个例子来说明变量和变量指向的引用(内存地址)实际上就是一个东西:
6c5a9eb8a2f114703f07663a7845ef5a.png
2be7a5583e8f500bb27afd697e4f0872.png

三、赋值

3.1 相同数据,不同变量名

讨论完Python的对象、属性和引用3个重要的概念之后,在正式介绍深浅拷贝之前,我们先讨论Python中的赋值。 在Python中,每次赋值都会开辟新的内存地址来存放数据,比如我们同时存放一个列表[1,2,3],即使数据是相同的,但是内存地址却不同:
7cba7cbf1ebf532258e2cc96a5db727a.png
其实就是两个不同的变量,只是恰好它们存放了相同的数据而已,但是存放的地址是不同的。
cc675c6f8f920d470dfc000cda9b483c.png
我们给v1列表追加了一个元素,发现它的内存地址是不变的,当然v2肯定是不变的:
918047dbf9820c556702fca76725c44c.png
f7c9e7f79d51d558af98d2a192c2ecdc.png

3.2 一个变量多次赋值

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

3.3 变量赋值

将一个变量赋值给另一个变量,其实它们就是同一个对象:数据相同,在内存中的地址也相同:
706cabb0c0441b0b92b22df030b9156b.png
252395034b1ba18b3fafbfe03905f86c.png
当我们给V1追加一个元素,V2也会同时变化:
6993c72465a0c3fc84260fcf57906f44.png
实际上它们就是同一个对象!!!!

3.4 嵌套赋值

如果是列表中嵌套着另外的列表,那么当改变其中一个列表的时候,另一个列表中的也会随着改变:
2eb2cd01b003ded4187c5a3a0956a1a2.png
原始数据信息:
2f10c4a5ea4f6ccf81948a205ba1af2f.png
当我们给v1追加了新元素之后:
c2971e13fc348c5579e1017cd99b8453.png
总结: 赋值其实就是将一个对象的地址赋值给一个变量,使得变量指向该内存地址。

四、浅拷贝

在Python中进行拷贝之前,我们需要导入模块:
import copy
⚠️浅拷贝只是拷贝数据的第一层,不会拷贝子对象。

4.1 不可变类型的浅拷贝

如果只是针对不可变的数据类型(字符串、数值型、布尔值),浅拷贝的对象和原数据对象是相同的内存地址:
96c2b3c63c5b1ecc4ee7677737c4d1cd.png
d3035aa4d17f3c713b6d0ced7f5be912.png
从上面的结果中我们可以看出来:针对不可变类型的浅拷贝,只是换了一个名字,对象在内存中的地址其实是不变的。
3521ac813162df74c4ca2bbac7d36dff.png
image-20201115225938833

4.2 可变类型的浅拷贝

首先我们讨论的是不存在嵌套类型的可变类型数据(列表、字典、集合): 237911e33d1ab32580d56a1fb08b6f7a.png
从上面的例子看出来:
  • 列表本身的浅拷贝对象的地址和原对象的地址是不同的,因为列表是可变数据类型。
  • 列表中的元素(第1个元素为例)和浅拷贝对象中的第一个元素的地址是相同的,因为元素本身是数值型,是不可变的。
通过一个图形来说明这个关系:
c9a37eafc8cf3ab5aa6b0b646ea754e2.png
字典中也存在相同的情况:字典本身的内存地址不同,但是里面的键、值的内存地址是相同的,因为键值都是不可变类型的数据。
3fd1fe88649ccdebdc6461f015a84a5d.png
如果可变类型的数据中存在嵌套的结构:
1d827e3ffd76d9c563b914e7afdf59e0.png
从上面的两个例子中我们可以看出来: 在可变类型的数据中,如果存在嵌套的结构类型,浅拷贝只复制最外层的数据,导致内存地址发生变化,里面数据的内存地址不会变。

五、深拷贝

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

5.1 不可变类型的深拷贝

关于不可变类型的深浅拷贝,其效果是相同的,具体看下面的例子:
e7bc36fa4c60b97bd097108032c064cc.png
c06de31c9dfe764b393f761389873fca.png
c4b5714b82371449f5969d17b31c439d.png
我们得出一个结论:针对不可变数据类型的深浅拷贝,其结果是相同的。

5.2 可变类型的深拷贝

首先我们讨论的是不存在嵌套的情况: 针对列表数据:
0d8d9512abdbfa677fcb0fe64dc0bc5b.png
e06d3fd10e3e627b7c64635dbbcf707a.png
针对字典数据:
55f4bb9159d75691aea9d13e72a986ab.png
2c1d263d0b51f064ef26f96cc1154bf1.png
我们可以得出结论:
  • 深拷贝对最外层数据是只拷贝数据,会开辟新的内存地址来存放数据。
  • 深拷贝对里面的不可变数据类型直接复制数据和地址,和可变类型的浅拷贝是相同的效果。
c8ffb51efc34892f2a31d8d55c01013c.png
我们讨论存在嵌套类型的深拷贝(以列表为例)。
db1d09500fba71433de712ab127d63a9.png
结论1: 对整个存在嵌套类型的数据进行深浅拷贝都会发生内存的变化,因为数据本身是可变的。
e59499c9fe98784c42d8b69b5b4a2e8f.png
结论2: 我们查看第一个元素1的内存地址,发生三者是相同的,因为1是属于数值型,是不可变类型。
a8307965ef98ca314cfe346f14157488.png
结论3: 我们查看第三个元素即里面嵌套列表的内存,发现只有深拷贝是不同的,因为这个嵌套的列表是可变数据类型,深拷贝在拷贝了最外层之后还会继续拷贝子层级的可变类型。
0977f8cb0d3d76e83885262e5a4753e3.png
结论4 :我们查看嵌套列表中的元素的内存地址,发现它们是相同的,因为元素是数值型,是不可变的,不受拷贝的影响。

六、元组的深浅拷贝

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

6.1 不存在嵌套结构

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

6.2 存在嵌套结构

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

七、is和==

在文章的开始就已经谈过:在Python中每个变量都有自己的标识、类型和值。每个对象一旦创建,它的标识就绝对不会变。一个对象的标识,我们可以理解成其在内存中的地址。is()运算符比较的是两个对象的标识;id()方法返回的就是对象标识的整数表示。 总结:is()比较对象的标识;==运算符比较两个对象的值(对象中保存的数据)。在实际的编程中,我们更多关注的是值,而不是标识本身。 第一个例子:我们创建了两个不同的对象,只是它们的值刚好相同而已。
472de698d361aa32bfdc65710ac377f6.png
19f96a983309e71fca92fdac2679ad37.png
第二个例子:我们先创建了一个对象v3,然后将他赋值给另一个对象v4,其实它们就是相同的对象,所以标识(内存地址)是相同的,只是它们的名字不同而已。
cfe737b08827bcccacbc0fc9bf35fd51.png
7f63d5e6f6ee0298b3b6d8aa94ecb4cd.png

总结

通过大量的例子,我们得出结论:
  • 在不可变数据类型中,深浅拷贝都不会开辟新的内存空间,用的都是同一个内存地址。
  • 在存在嵌套可变类型的数据时,深浅拷贝都会开辟新的一块内存空间;同时,不可变类型的值还是指向原来的值的地址。
不同的是: 在嵌套可变类型中,浅拷贝只会拷贝最外层的数据,而深拷贝会拷贝所有层级的可变类型数据。

转自:Datawhale;

END

版权声明:本号内容部分来自互联网,转载请注明原文链接和作者,如有侵权或出处有误请和我们联系。


合作请加QQ:365242293  

数据分析(ID : ecshujufenxi )互联网科技与数据圈自己的微信,也是WeMedia自媒体联盟成员之一,WeMedia联盟覆盖5000万人群。

5ee748abe18427cdfbabf566f2f80eca.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值