本节内容
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]]
请看重点:对列表第二层进行修改
>>> 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语言,它把变量的值直接保存在变量的存储区,我们称之为值语义。
浅拷贝的原理和深拷贝的原理。