python有几种赋值_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)编程时,如果需要保留原数据,那么应该进行深拷贝,避免修改原数据。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值