本文是南大张莉老师《用Python玩转数据》的学习笔记。
在python中对可变可迭代对象进行修改,可能会用到深拷贝、浅拷贝相关的知识。
如,在原始数据集中删掉一些不满条件的元素:
>>> lst=[1,2,4,3,5]
>>> for x in lst:
if(x%2==0):
lst.remove(x)
>>> print(lst)
[1, 4, 3, 5]
预期的输出结果应该为[1,3,5],然而却不是。
这是因为for循环执行时获取可迭代对象,会一次性地产生一个迭代器,如果这个迭代对象本身是可变的,比如说它是一个列表,那么使用修改元素的方法,比如说remove()和insert()等,会对迭代器的迭代产生不可预知的影响。
对于for循环生成的迭代器,在迭代时,有一个指针在不断地往下指,但是如果在这个时候对迭代对象进行插入或删除操作,就会改变迭代器中元素的位置。例如在上面的例子中,删除元素2以后,4、3、5这三个元素会依次往前挪,4出于2原来的位置,原本该指向4的指针此时指向了3,错误就这样产生了。
解决的办法之一就是对可迭代对象进行拷贝。
>>> for x in lst[:]: #创建一个lst的浅拷贝
if(x%2==0):
lst.remove(x)
>>> print(lst)
[1, 3, 5]
在这个例子中就没有出现前面那种情况,这是因为for循环迭代的,和执行插入或删除等操作的是两个不同的对象,执行就不会出现问题。
再看另一个例子:
>>> x=[1,2,3]
>>> y=x
>>> y[0]=4
>>> x
[4, 2, 3]
只对列表y的元素进行了修改,但x也跟着变了。因为y和x都是对同一块空间的引用,因此修改了y后x也会变。
为避免这样的情况,也可创建一个对象的拷贝,例如创建一个对象的浅拷贝,可以用x[:]的写法,或列表的copy()方法。
法1:
>>> x=[4,2,3]
>>> z=x[:]
>>> z[0]=9
>>> x
[4, 2, 3]
法2:
>>> x=[1,2,[3,4]]
>>> y=x.copy()
>>> y[0],y[2][0]=9,9
>>> y
[9, 2, [9, 4]]
>>> x
[1, 2, [9, 4]]
只部分地达到了目的,x一级元素没有变,二级元素改变了,即浅拷贝让一级元素有了独立的空间,而二级元素仍然指向了被拷贝对象的二级元素的内存区域。浅拷贝只复制了一级元素而不是复制内部子对象。
与浅拷贝相对应的是深拷贝,它既复制父对象,又复制子对象,它的实现可使用copy模块找中的函数deepcopy
>>> x=[1, 2, [9, 4]]
>>> import copy
>>> z=copy.deepcopy(x)
>>> z
[1, 2, [9, 4]]
>>> z[0],z[2][0]=8,8
>>> z
[8, 2, [8, 4]]
>>> x
[1, 2, [9, 4]]
python默认的拷贝是浅拷贝。
深拷贝和浅拷贝主要应用在对一些可变可迭代对象进行修改的过程中,以及对第三方库的使用中。
字符串是不可变对象,它不会修改原始迭代器中的元素,所以不会出现像列表这样的现象:
>>> s='beautiful'
>>> for ch in s:
if ch in 'aeiou':
s=s.replace(ch,'')