Python中的赋值、浅拷贝、深拷贝

1.jpg

直接进入正题!

一.赋值“=”

python赋值操作的最终结果是将变量指向某个内存中的对象,只是引用。
但不同的赋值操作的中间过程是不一样的,另一篇文章已经对赋值操作做了详细说明:https://www.jianshu.com/p/521bdd67790e
总结起来就是:
1)“变量B=变量A”(变量A肯定已经指向某个对象了),对于变量之间的赋值,毫无悬念,两个变量最终指向同一个对象。
2)“变量A=对象”(如A = 'python'),对于这种情况,如果对象在内存中不存在,那么将新建这个对象,然后将变量A指向该对象;如果对象已经存在了,那情况就稍微复杂了,部分情况是将变量指向原有的对象,部分情况会新建建一个对象(即使内存中已经有了一个值完全相同的对象),然后将变量指向这个新的对象!

赋值过程.jpg

二.拷贝(复制)

所谓拷贝,是完完整整地产生一个一模一样的新对象,拷贝完成后,两者应该是独立的个体,不再有半点联系。按照这个逻辑,“赋值”操作完全不属于拷贝的范畴

三.浅拷贝

所谓“浅拷贝”,意思是,拷贝了,但又拷贝得不彻底。浅拷贝之后,新对象确实是生成了,但在某些情况下新对象还和被拷贝对象有千丝万缕的联系。

浅拷贝只拷贝了最外层数据,对于子数据对象没有拷贝,而只是一个引用(与原数据共用一个自数据对象),无论修改新数据的子对象还是元数据的子对象,另一个都会改变。

从其说明来看,对于非嵌套的数据(没有子对象),浅拷贝实际上是完全拷贝了一个独立的对象。

浅拷贝方法:某些对象自身的copy()方法、copy模块中的copy()方法、切片操作[:]。

实例:

1)浅拷贝“非嵌套数据对象”
>>>a = [1,2,3]
>>>b = a.copy() #浅拷贝a给b
>>>print(id(a))
>>>print(id(b))
41921864
41943176

a[0] = 'A' #修改a的元素
print('a={}'.format(a))
print('b={}'.format(b))
a=['A', 2, 3]
b=[1, 2, 3]

上例中[1,2,3]是一个非嵌套的列表数据,b = a.copy()是将a浅拷贝一份然后指向b,从id可以看出,b指向的是一个与a相互独立的对象。后面修改了a指向的对象元素后,b指向的对象没有变化,进一步说明了两者的独立性。copy模块中的copy()方法、切片操作[:]的结果与此例完全相同,不再赘述。


浅拷贝非嵌套数据.jpg
2)浅拷贝“嵌套数据对象”
>>>a = [1,2,['A','B']]
>>>b = a.copy()
>>>print(id(a))
>>>print(id(b))
36044936
36047688

>>>print(id(a[2][0]))
>>>print(id(b[2][0]))
34525456
34525456

>>>a[0] = 'P' #修改a的外圈元素
>>>a[2][0] = 'Q' #修改a的子对象元素
print('a={}'.format(a))
print('b={}'.format(b))
a=['P', 2, ['Q', 'B']]
b=[1, 2, ['Q', 'B']]

此例中[1,2,['A','B']]是个嵌套列表,b = a.copy()只会拷贝最外层数据给新的对象,然后指向b,而子对象['A','B']则不会拷贝,a和b共同指向该子对象。id(a)≠id(b),说明了a.copy()确实只拷贝了外层对象,而id(a[2][0])=id(b[2][0])说明子对象是共用的,没重新拷贝。后面a[0] = 'P'和a[2][0] = 'Q'分别修改了列表的外圈元素和子对象元素,从打印结果可以看出b的外圈元素没变,而内圈元素变了,进一步说明了外圈是独立对象而子对象是共享的。
copy模块中的copy()方法、切片操作[:]的结果与此例完全相同,不再赘述。


浅拷贝嵌套数据.jpg

四.深拷贝

有了浅拷贝的比较,深拷贝就很简单了,深拷贝才是真正的拷贝,无论多少嵌套,一股脑儿都拷贝一个新,是彻底的拷贝,拷贝以后与被拷贝对象不再有任何瓜葛。
深拷贝方法:copy中的deepcopy()方法

实例:

1)深拷贝“非嵌套数据对象”

这种情况与前面的“浅拷贝“非嵌套数据对象””一模一样,不再赘述。

2)深拷贝“嵌套数据对象”
import copy

>>>a = [1,2,['A','B']]
>>>b = copy.deepcopy(a) #深拷贝
>>>print(id(a))
>>>print(id(b))
42275400
42276680
>>>print(id(a[2][0]))
>>>print(id(b[2][0]))
34328848
34328848

上例中我们惊奇地发现id(a[2][0])=id(b[2][0]),这不是和浅拷贝一模一样吗?没有我们所谓的独立拷贝一个啊?这是怎么回事?一开始笔者也非常纳闷。研究发现实际上这是Python节省内存的方式,虽然是深拷贝,但我先不急着拷贝子对象,先共用,如果后面程序需要单独用到这些数据时,再执行拷贝子对象这个动作。接着上面的代码:

>>>a[2][0] = 'Q' #修改a的子对象元素
>>>b[2][1] = 'QQ' #修该b的子对象元素

>>>print('a={}'.format(a))
>>>print('b={}'.format(b))
a=['1', 2, ['Q', 'B']]
b=['1', 2, ['A', 'QQ']]

>>>print(id(a[2][0]))
>>>print(id(b[2][0]))
39764128
39764408

我们通过a[2][0] = 'Q'修改a的子对象的第一个元素,通过b[2][1] = 'QQ'修改b的子对象的第二个元素,根据输出结果a=['1', 2, ['Q', 'B']]和b=['1', 2, ['A', 'QQ']]两者确实相互不影响,说明是独立的。此时我们再输出第一个子对象的地址,发现id(a[2][0])≠id(b[2][0])了,说明此时子对象已经是两个独立的对象了,而且两者都不等于原来子对象的内存地址34328848。这个结果说明了两个问题:1)深度拷贝后,需要对子对象操作时,才会真正执行拷贝一个子对象的动作;2)深拷贝后,如果无需要对两个对象的子对象元素进行修改,那么两者都会先重新生成一个子对象(也就是这里的39764128和39764408),然后再修改,至于最开始的那个子对象(上面的34328848)则会被销毁;如果只是修改一个的子对象元素,那么另一个仍然指向最原始的子对象,而不会被销毁。

深拷贝嵌套数据.jpg

五.总结

1)赋值“=”操作,只是传递了一个引用,压根不是“拷贝”的范畴;
2)浅拷贝的作用是为了节省空间,对于非嵌套数据,浅拷贝和深拷贝实际是一样的;对于嵌套数据,浅拷贝只拷贝最外层数据,而共享子对象数据;
3)深拷贝完全拷贝真个对象,包括子对象;但并非直接生产两个独立的子对象,而是有一个共享子对象的中间过程(也是为了节省空间),当需要改变子对象时,才会最终执行单独拷贝子对象的操作,而且当两个对象都进行更改子对象操作时,两个对象都会重新建立新的子对象,而把最初的子对象抛弃;
4)编程时,如果需要保留原数据,那么应该进行深拷贝,避免修改原数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,065评论 1 32
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,242评论 8 265
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,270评论 0 6
  • 转眼2017年下半年都已经过去一个月了,上半年平平淡淡,想学手绘就零零散散的画了几个月也没有多大进步,想学的东西太...
    疯狂的小南瓜阅读 312评论 0 0
  • 《冰激凌快化了》 冰激凌快化了 赶紧吃 快快快 别催了 看,手上,嘴边,胸前全是 慢慢吃 吃到凌晨 吃到一只猫出来...
    锄风少年阅读 192评论 0 2