深拷贝和浅拷贝都是对象的拷贝,都会生成一个看起来相同的对象,他们本质的区别是拷贝出来的对象的地址是否和原对象一样,也就是地址的复制还是值的复制的区别。
使用浅拷贝,当原容器对象中可变对象中有元素发生变化,拷贝得到的对象也会变化。而使用深拷贝时,不会有这种问题。
可变对象和不可变对象:
1)可变对象是指,一个对象在不改变其所指向的地址的前提下,可以修改其所指向的地址中的值
2)不可变对象是指,一个对象所指向的地址上值是不能修改的,如果你修改了这个对象的值,那么它指向的地址就改变了,相当于把这个对象指向的值复制出来一份,然后做了修改后存到另一个地址上
深拷贝和浅拷贝需要注意的地方就是可变元素的拷贝:
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝它拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变元素指向同一个地址,所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。
代码示例:
import copy
l1=['a','b','c',[1,2,3],('x','y')]
l2=l1
l3=copy.copy(l1)
l4=copy.deepcopy(l1)
print("*"*50)
print("l1:{} id(l1):{} id(l1[3]):{} id(l1[4]):{}".format(l1,id(l1),id(l1[3]),id(l1[4])))
print("l2:{} id(l2):{} id(l2[3]):{} id(l2[4]):{}".format(l2,id(l2),id(l2[3]),id(l2[4])))
print("l3:{} id(l3):{} id(l3[3]):{} id(l3[4]):{}".format(l3,id(l3),id(l3[3]),id(l3[4])))
print("l4:{} id(l4):{} id(l4[3]):{} id(l4[4]):{}".format(l4,id(l4),id(l4[3]),id(l4[4])))
l1.append('new')
l1[3].append(4)
l1[4]=('t1','t2')
print("*"*50)
print("l1:{} id(l1):{} id(l1[3]):{} id(l1[4]):{}".format(l1,id(l1),id(l1[3]),id(l1[4])))
print("l2:{} id(l2):{} id(l2[3]):{} id(l2[4]):{}".format(l2,id(l2),id(l2[3]),id(l2[4])))
print("l3:{} id(l3):{} id(l3[3]):{} id(l3[4]):{}".format(l3,id(l3),id(l3[3]),id(l3[4])))
print("l4:{} id(l4):{} id(l4[3]):{} id(l4[4]):{}".format(l4,id(l4),id(l4[3]),id(l4[4])))
运行结果为:
**************************************************
l1:['a', 'b', 'c', [1, 2, 3], ('x', 'y')] id(l1):1274284587272 id(l1[3]):1274284587336 id(l1[4]):1274282221128
l2:['a', 'b', 'c', [1, 2, 3], ('x', 'y')] id(l2):1274284587272 id(l2[3]):1274284587336 id(l2[4]):1274282221128
l3:['a', 'b', 'c', [1, 2, 3], ('x', 'y')] id(l3):1274284587208 id(l3[3]):1274284587336 id(l3[4]):1274282221128
l4:['a', 'b', 'c', [1, 2, 3], ('x', 'y')] id(l4):1274284587144 id(l4[3]):1274284587464 id(l4[4]):1274282221128
**************************************************
l1:['a', 'b', 'c', [1, 2, 3, 4], ('t1', 't2'), 'new'] id(l1):1274284587272 id(l1[3]):1274284587336 id(l1[4]):1274283219656
l2:['a', 'b', 'c', [1, 2, 3, 4], ('t1', 't2'), 'new'] id(l2):1274284587272 id(l2[3]):1274284587336 id(l2[4]):1274283219656
l3:['a', 'b', 'c', [1, 2, 3, 4], ('x', 'y')] id(l3):1274284587208 id(l3[3]):1274284587336 id(l3[4]):1274282221128
l4:['a', 'b', 'c', [1, 2, 3], ('x', 'y')] id(l4):1274284587144 id(l4[3]):1274284587464 id(l4[4]):1274282221128
结果分析:
1)列表l1和l2是赋值操作,两个对象完全指向同一个地址,l1和l2就是同一块地址的两个引用,其实就是一个东西,所以一个对象在修改浅层元素(不可变)或深层元素(可变)时,另一个对象也同时在变
2)l3是l1进行浅拷贝生成的对象,两个对象整体的id是不同的,但是里面的列表的地址却是相同的(指向同一个地址),所以l1在浅层次元素层面(不可变)增加一个元素时,l3并没跟着增加,但是l1的列表在增加一个元素时,l3的列表也跟着增加了,这就是因为l1和l3的列表是指向同一个地址的,这个地址上的值变了,在两个地方会同时改变
l1和l3的集合虽然指向同一个地址,但是集合是不可变元素,l1修改了集合就相当于修改了地址,
id(l1[4]):1274282221128
修改为:
id(l1[4]):1274283219656
3)l4的浅层次元素(不可变)和 深层次元素(可变)的地址和l1都不一样,所以,l1无论怎么修改,l4都不会跟着改变,这就是深拷贝的结果
总结:
深拷贝就是完全跟以前就没有任何关系了,原来的对象怎么改都不会影响当前对象
浅拷贝,原对象的可变元素改变的话会改变当前对象,如果当前对象中可变元素改变了,也同样会影响原对象
赋值 浅拷贝 深拷贝辨析
在python中,对象赋值实际上是对象的引用。
当创建一个对象,然后把它赋给另一个变量的时候, python并没有拷贝这个对象,而只是拷贝了这个对象的引用。如果需要拷贝对象,需要使用标准库中的 copy模块。
copy模块提供copy和deepcopy两个函数:
copy浅拷贝:拷贝一个对象,但是对象的属性还是引用原来的。对于可变类型,比如列表和字典,只是 复制其引用。基于引用所作的改变会影响到被引用对象。
deepcopy深拷贝:创建一个新的容器对象, 包含原有对象元素(引用)全新拷贝的引用。外围和内部元素都拷贝对象本身,而不是引用。
注意对于数字、字符串和其他原子类型对象等,没有被拷贝的说法。如果对其重新赋值,也只是新创建 一个对象,替换掉旧的而已。