Python | 深浅拷贝(python深浅拷贝原理)
off999 2024-10-29 15:00 39 浏览 0 评论
同驻留机制一样,深浅拷贝在编程中的应用其实也不是很多。但是我们依然需要了解。一方面是为了避免后期开发代码时出现问题却不知道原因,另一方面也是因为面试时几乎都会问到有关深浅拷贝的问题。
深浅拷贝主要分为三个方面:
- 什么是赋值
- 什么是浅拷贝
- 什么是深拷贝
赋值
变量间赋值操作就是将多个变量的内存地址指向同一个数据的内存地址。例如,对于下面的代码:
a = 10
b = a
print(id(a), id(b))输出的结果为:
1947042192 1947042192a 和 b 的内存地址相同。这段赋值操作在内存中的程序是:首先,在内存中开辟一块内存空间储存整数 10,它的内存地址为 1947042192;然后,变量名 a 指向这个内存空间的内存地址;最后,变量名 b 同样指向这个内存地址。
对于整型数据这样的不可变数据类型来说,此时,如果重新给 a 进行赋值,比如赋值为 11。将会新开辟一块内存空间来存储整型数据 11,然后 a 将指向 11 的内存地址(比如:1947042224),原来的指向将不再存在。而 b 仍然指向 10 的内存地址 1947042192:
用代码来验证就是:
a = 10
b = a
a = 11
print(id(a), id(b))输出的结果为:
1947042224 1947042192对于可变数据类型的赋值操作则会有些例外。赋值操作的原则都是相同的,新变量名会指向已经存在的数据,而不是新开辟一块内存空间:
lst = [1, 2, 3]
lst1 = lst
print(id(lst), id(lst1))
for i in lst:
print(id(i))输出的结果为:
1334279969672 1334279969672
1947041904
1947041936
1947041968首先整型数字 1、2 和 3 分别开辟各自的内存空间。需要注意的是,列表中存储的是每个元素的内存地址,而不是元素本身。当整型数据内存空间开辟好了之后,又开辟了一块列表空间,其中存储的数据是三个整型数字的内存地址。然后,变量 lst 指向列表的内存地址 1334279969672。最后,赋值操作令变量 lst1 也指向该列表的内存地址 1334279969672。
这些操作看起来繁琐许多,但实际上的原理还是赋值操作只是将不同的变量指向同一个数据的内存地址。
但是现在,如果我们对 lst1 进行修改,比如增加一个新的元素 4,会发生什么呢?
lst = [1, 2, 3]
print(id(lst))
lst1 = lst
lst1.append(4)
print(lst, lst1)
print(id(lst), id(lst1))
for i in lst:
print(id(i))输出的结果为:
3009179196296
[1, 2, 3, 4] [1, 2, 3, 4]
3009179196296 3009179196296
1947041904
1947041936
1947041968
1947042000我们发现发生了一件奇怪的事情:虽然我们只对 lst1 进行了修改,但是 lst 也同样发生了改变。
这是因为列表是可变数据类型,当我们对列表进行操作时,不会改变列表的内存地址 ^1。使用 append 方法会在列表存储的内容中添加一个整型数据 4 的内存空间,但并不会改变变量 lst 和 lst1 指向列表内存的状态。故而,虽然只对 lst1 进行了修改,但是查看 lst 时,变化是相同的。
举一个形象一点的例子就是,列表就好比是一个书包。最开始,这个书包归小明所有。后来,小明跟好朋友小芳共用了这个书包。小芳有一天在书包里装了一本书,虽然小明没有对这个书包做任何的操作,他依然能够看到自己的书包里多出了一本书来。
对于字典等其他可变数据类型,道理都是相同的。对于可变数据类型的嵌套,也是一样:
dic = {'key1': 2, 'key2': [1, 2, 3]}
dic1 = dic
dic1['key2'].append(4)
print(dic, dic1)输出的内容为:
{'key1': 2, 'key2': [1, 2, 3, 4]} {'key1': 2, 'key2': [1, 2, 3, 4]}总结起来就是:赋值就是让多个变量指向同一个内存地址,如果这个内存地址的数据是不可变数据类型,修改时会开辟新的内存空间(字符串,数字,布尔值,元组);如果时可变的数据类型,会在原地址进行修改(列表,字典)。
浅拷贝
与赋值略有差异的是,浅拷贝会将数据的外壳新开辟一块内存空间,在新的内存空间中存储的是最外层元素的内存地址。
列表有一个 .copy() 方法,用来进行浅拷贝操作:
lst = [1, 2, [3, 4]]
lst1 = lst.copy() #拷贝 or 复制
print(lst, lst1)
print(id(lst), id(lst1))
print(id(lst[0]), id(lst1[0]))
print(id(lst[1]), id(lst1[1]))
print(id(lst[2]), id(lst1[2]))
print(id(lst[2][0]), id(lst1[2][0]))
print(id(lst[2][1]), id(lst1[2][1]))输出的结果为:
[1, 2, [3, 4]] [1, 2, [3, 4]]
2718491515720 2718491515912
1945534576 1945534576
1945534608 1945534608
2718491515784 2718491515784
1945534640 1945534640
1945534672 1945534672我们看到,除了最外层的壳子,也就是列表本身被新开辟了一块内存空间之外,无论列表中的元素是否可变,都只是复制了原来列表中元素的内存地址。
最开始,列表 lst 开辟了一块地址为 2718491515720 的内存空间,其中,数字 1 被存储在地址 1945534576,数字 2 被存储在地址 1945534608,列表被存储在地址 2718491515784。列表中的数字 3 被存储在地址 1945534640,列表中的数字 4 被存储在地址 1945534672。当进行浅拷贝之后,为 lst1 新开辟一块内存地址为 2718491515912 的内存空间,其中存储的元素的内存地址跟 lst 中的元素相同。
此时,如果我们对 lst1 进行增加操作,比如增加一个元素 5:
lst1.append(5)
print(lst, lst1)输出的结果为:
[1, 2, [3, 4]] [1, 2, [3, 4], 5]只有 lst1 中增加了元素,lst 并没有发生变化。这是因为 lst 和 lst1 的内存地址不同,对 lst1 进行操作并不会影响到 lst。
不过如果我们对列表中嵌套的列表进行增加操作,比如:
lst = [1, 2, [3, 4]]
lst1 = lst.copy()
lst1[-1].append(5)
print(lst, lst1)输出的结果为:
[1, 2, [3, 4, 5]] [1, 2, [3, 4, 5]]不管是 lst 还是 lst1,它们的最后一个元素都是指向的列表 [3, 4] 的内存地址 2718491515784。当对这个列表进行修改时,尽管是使用 lst 进行的操作,lst1 调用列表时,仍然能发现列表的变化。
除了使用 .copy() 方法,我们还可以通过切片来实现浅拷贝:
lst = [2, 3, [4, 5], 6]
lst1 = lst
lst2 = lst[:]
lst1[2].append(6)
print(lst, lst1, lst2)输出的结果为:
[2, 3, [4, 5, 6], 6] [2, 3, [4, 5, 6], 6] [2, 3, [4, 5, 6], 6]深拷贝
深拷贝之后的数据使用同我们主观认为的基本一致:对一个变量的修改不会影响到另一个变量的值。经过深拷贝后的两个变量虽然值相同,但是不受彼此影响。
当然,这也不意味着深拷贝后的两个变量中的每个元素都是不同的。为了节省内存,经过深拷贝后的不可变元素仍然被两个变量共用。因为对不可变元素的改变一定会造成内存地址的变化,所以不需要单独开辟内存空间。但是对于可变元素,不管嵌套多少层,都会开辟新的内存空间。
调用深拷贝方法需要导入一个 copy 模块:
import copy
lst = [1000, 2, [3, 4]]
lst1 = copy.deepcopy(lst)
print(id(lst), id(lst1))
print(id(lst[0]), id(lst1[0]))
print(id(lst[1]), id(lst1[1]))
print(id(lst[2]), id(lst1[2]))
print(id(lst[2][0]), id(lst1[2][0]))
print(id(lst[2][1]), id(lst1[2][1]))返回的结果为:
2277137687880 2277137689160
2277135326064 2277135326064
1945534608 1945534608
2277137687688 2277137689096
1945534640 1945534640
1945534672 1945534672与我们前面谈到的一样,深拷贝后,两个变量会共用不可变数据,而可变数据类型则会开辟新空间。
深浅拷贝总结
赋值:多个变量名指向同一个内存地址,也就是将变量和值在内存中形成映射指向关系
浅拷贝:只对最外层的壳子开辟内存空间,拷贝第一层元素的内存地址。(列表中存储的是数据的内存地址,我们能看到数据是因为程序通过内存地址找到值,然后显示了出来)
深拷贝:不可变数据共用,可变数据类型不管嵌套多少层都会开辟新空间
相关推荐
- 163免费邮箱入口(163免费邮箱入口官网)
-
手机163的登录入口163邮箱官网入口是;https://smart.mail.163.com/login.htm网易邮箱注册方法;1在浏览器搜索“网易邮箱”。2进入网易邮箱官网https://sma...
- 一笔签名设计免费版(设计自己的签名免费版)
-
我国法律没有规定签名时要求一笔一划的写,因此,怎么签名这是公民的自由,合同方式协商一致即可。 但是,在实践中,双方当事人签合同时,建议写工整,能让他人认识签名的是谁,另外,最好在名字上按个手印,...
- win10正式版下载官网(win10官网免费下载)
-
先看系统之家之类的系统盘网站提供的系统盘文件,都是Ghost系统盘。所有的Ghost系统盘都是网络上的电脑系统高手以Ghost系统映像文件为基础制作的系统安装盘,在必要的windows安装系统的基础...
- adobe indesign(adobe indesign 下载)
-
在InDesign中,您可以通过使用数据合并功能来快速导入全年日历。首先,您需要准备一个包含所有日期和相应格式的数据表格,然后在InDesign中创建一个日历模板。接下来,使用数据合并功能将数据表格与...
- qq32位版本下载官方(qq3.3.3版本下载)
-
1152活跃天。QQ等级升级需要的活跃天数和自然的日是完全不同的,活跃天并不等于自然天数。活跃天是需要满足加速条件后才能获得的,相当于某些业务的成长值,经验值等,QQ等级由四个标识图展示,从低到高分别...
- 最新版本qq手机版下载安装免费
-
你说的是下载QQ软件吗?现在手机应用市场里都有QQ软件下载的,或者直接百度也可以。不用。苹果手机在appstore里下载手机qq步骤如下:1.在苹果手机中打开自带的AppStore软件进入;2.按...
- 全国34个卫视频道(全国34个卫视频道节目单)
-
全国卫视频道不止34个。全国各地28大省级卫视:北京卫视·天津卫视·河北卫视·山西卫视·内蒙古卫视·辽宁卫视·吉林卫视·黑龙江卫视·江苏卫视·浙江卫视·安徽卫视·江西卫视·山东卫视·河南卫视·湖北卫视...
- 福昕pdf阅读器电脑版(福昕pdf阅读器电脑版怎么用)
-
这是因为你的pdf文件没有与福昕阅读器进行关联。你如果要想双击pdf文档后使用福昕阅读器打开,可以这样设置一下::选中pdf文档,右击鼠标,在快捷菜单中点击属性。点击“属性”对话框中“打开方式”右边的...
- 快吧游戏官网(快吧游戏中心)
-
1.快吧游戏可以通过官方网站或应用商店进行下载。2.官方网站提供了游戏的下载链接,也可以通过扫描二维码进行下载。快吧游戏是一款手机游戏,可以在应用商店中下载安装。以下是在iOS和Android系统...
- 最开放的浏览器下载(最开放的浏览器下载软件)
-
可以直接在浏览器设置内打开下载目录进行查看,步骤如下:所需材料:华为6X、华为浏览器示例。一、因为所有浏览器的设置基本一至,这里以华为浏览器为例,首先点击打开华为浏览器。二、打开浏览器后,点击右下角的...
- 万年历2025最新版(万年历2025最新版属相)
-
2024年正月初十对应的阳历出生日期是2024年2月1日。如果按照周岁来计算,他们属龙。辰龙(甲辰年):在中国传统文化中,龙象征着权力、尊贵和神圣。
- 163邮箱登录网页版(163邮箱登录网页版官网)
-
http://mail.163.com步骤一:输入邮箱官网网址http://mail.163.com/打开163邮箱登录入口界面。步骤二:输入已有的邮箱账号、密码,点击登录即可。步骤三:如果没有邮箱账...
- 青骄第二课堂学生登录入口(青骄第二课堂,登录入口)
-
青骄第二课堂学生账号注册是不需要学生注册的,是经过每个学校的老师就是班主任注册之后,学生才能登录首先我们要打开自己的微信,点击打开微信右上方的加号点击添加好友,点击公众号,搜索青椒第二课堂点...
欢迎 你 发表评论:
- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
