美文网首页
Python中的赋值、浅拷贝、深拷贝

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

作者: 马尔代夫Maldives | 来源:发表于2018-09-23 08:19 被阅读0次
    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)编程时,如果需要保留原数据,那么应该进行深拷贝,避免修改原数据。

    相关文章

      网友评论

          本文标题:Python中的赋值、浅拷贝、深拷贝

          本文链接:https://www.haomeiwen.com/subject/lutfoftx.html