一、对象拷贝引出
在日常工作中,我们经常会需要拷贝一个对象。例如:写文档时,需要对文档进行修改,一般我们会创建一个副本,在副本上进行修改。编程也一样经常需要复制一份对象,按照目前我们已学的知识。可能会这么写:
>>> l = ['小王', '小明', ['小刚', '小红']]
>>> l1 = l
>>> l1[0] = '大王'
>>> print(l, l1)
['大王', '小明', ['小刚', '小红']] ['大王', '小明', ['小刚', '小红']]
通过这种方式,l1 确实和 l 对象一样。但是这只是另起了一个变量名对原来内存空间的引用。一旦修改新对象,原对象也就一起修改了。但是我们需要修改新对象不影响原对象,这时候就有了深浅拷贝的概念。
二、浅拷贝
浅拷贝通过copy函数实现。
>>> l = ['小王', '小明', ['小刚', '小红']]
>>> l2 = l.copy()
>>> print(l2, id(l), id(l2))
['小王', '小明', ['小刚', '小红']] 4498268864 4499594240
>>> print(id(l[0]), id(l[1]), id(l[2]))
4498662000 4498662096 4498626624
>>> print(id(l2[0]), id(l2[1]), id(l2[2]))
4498662000 4498662096 4498626624
从这个示例中可以看出,通过copy函数进行对列表l的拷贝。产生了一个新的列表,但是两个列表里面的元素的id是一致的。说明copy只是拷贝了容器本身。我们来修改了一下新列表。
>>> l2[0] = '大王'
>>> l2[1] = '大明'
>>> l2[2][0] = '大刚'
>>> l2[2][1] = '大红'
>>> print(l, l2)
['小王', '小明', ['大刚', '大红']] ['大王', '大明', ['大刚', '大红']]
看执行结果,新列表l2所有元素都被改变了,但是原列表l只有子列表改变了。这是因为浅拷贝只会把原列表的第一层里面的索引和内存地址拷贝到新的内存空间中。又因为前两个元素是字符串属于不可变类型。因此在修改l2时,又申请了新的内存空间存储字符串。但是原列表还是指向的原来的内存地址没有被改变。同样的第三个元素是子列表可变类型,当修改子列表的元素时,子列表自身的内存地址并没有改变。
总之浅拷贝创建一个新对象,但不会递归复制嵌套对象。新对象的内容是对原对象中元素的引用。
三、深拷贝
深拷贝需要通过copy模块中的deepcopy函数实现
>>> l = ['小王', '小明', ['小刚', '小红']]
>>> import copy
>>> l3 = copy.deepcopy(l)
>>> print(l3, id(l), id(l3))
['大王', '大明', ['大刚', '大红']] 4499593856 4499594240
>>> print(id(l[0]), id(l[1]), id(l[2]))
4498662288 4499292880 4499594176
>>> print(id(l3[0]), id(l3[1]), id(l3[2]))
4498662288 4499292880 4498693568
可以看出,深拷贝在复制对象时,针对可变类型会产生新的容器,针对不可变类型会使用原值。这样可以节省内存空间。我们尝试修改新对象的值:
>>> l3[0] = '大王'
>>> l3[1] = '大明'
>>> l3[2][0] = '大刚'
>>> l3[2][1] = '大红'
>>> print(l, l3)
['小王', '小明', ['小刚', '小红']] ['大王', '大明', ['大刚', '大红']]
修改新列表的值,完全不对原列表有影响。因此我们可以得出结论,深拷贝会递归复制所有嵌套对象,生成一个完全独立的新对象。修改原对象或深拷贝对象中的嵌套对象不会相互影响。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付
