人生苦短,我用python,基础(3) --- python深浅拷贝详解

本节内容

    1、浅拷贝

    2、深拷贝

一、浅拷贝( copy )详解                                                                                                     

浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象,即只拷贝第一层。

(1) 浅拷贝一个无嵌套列表进行拷贝

>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]
>>> import copy
>>> names1 = names.copy()        #通过copy对names的内容进行复制并赋给names1
>>> names1
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]    #names1与names一模一样

对copy得到的列表进行修改,发现对原列表并影响(注意:在‘浅拷贝一个无嵌套列表进行拷贝’的前提)

>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]
>>> names1
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]
>>> names1[1] = '改改'
>>> names1           # copy得到的列表已经修改成功
['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]
>>> names            # 原列表names的值没有改变
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]    

对原列表进行修改,也并不会对copy得到的列表有影响(注意:在‘浅拷贝一个无嵌套列表进行拷贝’的前提)

总结:在对一个无嵌套的列表进行浅拷贝时,对原列表还是目标列表进行操作都不会影响另一个。

(2)浅拷贝一个有嵌套的两层列表

>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]
>>> name1 = names.copy()            #得到一样内容的列表
>>> name1
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]

对copy得到的列表的第一层进行修改,不会对另一列表有影响

>>> name1[0] = 111
>>> name1
[111, 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]
>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]

同理对原列表第一层修改也不会影响另一列表

>>> names1 = names.copy()
>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]
>>> names1
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]
>>> names[-1] = 0
>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, 0]
>>> names1
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]

请看重点:对列表第二层进行修改

扫描二维码关注公众号,回复: 1624604 查看本文章
>>> names = names1.copy()
>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]
>>> names1
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改改', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]
>>> names[-1][1] = '改了吗'
>>> names
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改了吗', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]
>>> names1
['gjaklj', 'haj', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6, ['gjaklj', '改了吗', 'hghdkl', 'r3', 1, 5, 9, 7, 8, 6]]

发现了吗?

总结:对列表进行浅拷贝时,当列表有多层时,操作较深层次时,会对另一列表有影响,即相当于同时操作的两类表(更多列表同理)

想知道原因?----->> 请往下看

二、深拷贝( deepcopy )详解                                                                                               

深拷贝(deepcopy): copy模块的deepcopy方法,完全拷贝了父对象的及其子对象。

语法 :

deep_obj = copy.deepcopy(obj)

(1) 对于无嵌套的列表(或其他数据类型)进行深拷贝时,结果同浅拷贝一样,再此就不给出跟多的解释。

(2) 对于有嵌套的列表进行深拷贝

>>> names
[1, 2, 3, 4, 5, 6, [1, 1, 2, 2, 3, 3]]
>>> names1 = copy.deepcopy(names)        # 进行深拷贝
>>> names1
[1, 2, 3, 4, 5, 6, [1, 1, 2, 2, 3, 3]]
>>> names[0] = 0                         #修改第一层的元素
>>> names
[0, 2, 3, 4, 5, 6, [1, 1, 2, 2, 3, 3]]
>>> names1
[1, 2, 3, 4, 5, 6, [1, 1, 2, 2, 3, 3]]   # 没影响
>>> names[-1][0] = 0                     # 修改深层次的元素
>>> names
[0, 2, 3, 4, 5, 6, [0, 1, 2, 2, 3, 3]]
>>> names1
[1, 2, 3, 4, 5, 6, [1, 1, 2, 2, 3, 3]]   # 无影响

总结:当对列表(或其他数据类型)进行深拷贝,对列表的第一层或更深层次的操作都不会对另一列表有影响。

三、 深浅拷贝的原理及对比                                                                                                   

python中的对象之间复制是按引用传递的。如有需要则需要使用标准库的copy模块。

对于python而言,python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的值本身。

什么是引用语义呢?和引用语义对应的是什么呢?

引用语义:在python中,一个变量保存的是对象(值)的引用,称为引用语义。该方式,变量所需要的存储空间大小一致,因为改变量保存的是一个引用即对象(值)的地址。(这应该是python不用声明变量类型的原因)

对应的位值语义,那么值语义又是什么呢?

值语义:比如c语言,它把变量的值直接保存在变量的存储区,我们称之为值语义。采用这种方式,每一份变量在内存所占的空间就要根据变量实际的大小而定,无法固定给每个变量分配大小。(应该是c语言在使用变量前必须声明类型,把不同类型的值赋给变量会出错的原因)

下面来一张简单易懂的图理解一下python的引用语义和c语言的值语义在内存的存放情况,


从图中可以看出来,python的变量中存储的是变量的值存放的地址,而c语言则是直接把值存储在变量的空间中。

由于python都是采用引用的方式,而数据结构又可以包含基础数据类型(int,long,char等),使得在python中的数据的存储都是如下图这种情况,即每个变量中都存储了这个变量的值的地址,而非值本身;所以对于复杂的数据结构来说,里面存储的也只是每个元素的地址而已。


1、数据类型的重新初始化对python引用语义的影响

对于python,变量的每一次初始化(或重新赋值),都会开劈一个新的空间,并将新的值的地址赋值给变量(即变量存储为新的地址值),请看例子:

>>> name = 'sbn'
>>> print(id(name))
3266710568552
>>> name = 'hdw'
>>> print(id(name))
3266709092648

                                

可以看出,name在重复的初始化过程中,其id会变化,即在内存中实现了如上图的转变。

浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象,即只拷贝第一层。一张图来了解一下:   


                  图一                                          图二

上图一对应的值为:

>>> sourcelist
['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5']]

上图二对应的值和关系为:

>>> sourcelist
['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5']]
>>> copylist = sourcelist.copy()
>>> copylist
['str1', 'str2', 'str3', 'str4', 'str5', ['str1', 'str2', 'str3', 'str4', 'str5']]

从上面的代码来看,suorcelist和copylisst一样,其实在内存中已经是生成了新的列表,copy了suorcelist,获得了一个新的列表copylist,存储了5个字符串和一个列表所在内存的地址。如图二


从上面的代码我们可以看出,对于sourceLst和copyLst列表添加一个元素,这两个列表好像是独立的一样都分别发生了变化,但是当我修改lst的时候,这两个列表都发生了变化,这是为什么呢?我们就来看一张内存中的变化图:


        可以知道sourceLst和copyLst列表中都存储了一坨地址,当我们修改了sourceLst1的元素时,相当于用'sourceChange'的地址替换了原来'str1'的地址,所以sourceLst的第一个元素发生了变化。而copyLst还是存储了str1的地址,所以copyLst不会发生改变。

  当sourceLst列表发生变化,copyLst中存储的lst内存地址没有改变,所以当lst发生改变的时候,sourceLst和copyLst两个列表就都发生了改变。

  这种情况发生在字典套字典、列表套字典、字典套列表,列表套列表,以及各种复杂数据结构的嵌套中,所以当我们的数据类型很复杂的时候,用copy去进行浅拷贝就要非常小心。。。

总结:python都是引用语义,即变量存储的是时实际值的地址,而在进行浅拷贝时,只是拷贝了数据的第一层,也就是说你浅拷贝列表时,其实得到的是原列表中存储的变量的值的地址,所以新的列表内的元素还是指向值的地址,而当为多层嵌套的数据结构时,你还是指向里层的内存的地址,所以你改变里层的元素时,就会作用到所有的列表。


深拷贝(deepcopy): copy模块的deepcopy方法,完全拷贝了父对象的及其子对象。                                                        

        刚才你已经了解了浅拷贝的原理,现在来就来看看深拷贝的原理吧!

        你写程序时,希望复杂的数据结构之间完全copy一并且他们之间有没有联系了,对任一个操作都不会影响另一个,这上你就可以使用深拷贝了。

        那什么是深拷贝呢?

        深拷贝就是python的copy模块提供的一个deepcopy方法。深拷贝会完全复制原变量相关的所有数据,会在内存中生成一套完全一样的内容,即假如你有一个双层的嵌套的列表,那对于第二层,不会只是拷贝指向第二层列表的地址的值,而是把第二层列表里的值也拷贝到目标变量。在这个过程中,得到的两个变量你对任意的一个进行修改都不会影响到其他的变量。下面看看实践:


从结果中发现,对一个进行操作,另一个并没有影响。下面来看看上面代码的变量在内存中的状况吧。


        从上面的内容,可以明白的知道深拷贝的原理,深拷贝其实就是在内存中重新开辟一块空间,不管数据结构有多复杂,只要是可能与到发生改变的的数据类型,就重新开辟一块内存空间把内容复制下来 ,一直往里执行下去,直到最后一层,当不再有复杂的数据结构时,就保存其引用。这样,不管数据结构有多复杂,数据之间的任意操作都不会互相影响。

总结                                                                                                                                                             

直接赋值:其实就是对象的引用(别名)。

浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象,即只拷贝第一层。

深拷贝(deepcopy): copy模块的deepcopy方法,完全拷贝了父对象的及其子对象。

引用语义在python中,一个变量保存的是对象(值)的引用,称为引用语义。

值语义:比如c语言,它把变量的值直接保存在变量的存储区,我们称之为值语义。

浅拷贝的原理和深拷贝的原理。

参考 python---赋值与深浅拷贝  

猜你喜欢

转载自blog.csdn.net/perfect1t/article/details/80454024