背景
开发工作中,有时候我们希望可以快速复制一个对象,python封装了内置函数copy模块中有copy与deepcopy函数,其中 copy是浅拷贝,deepcopy是深拷贝。在学习这俩个点时 我们需要弄清楚以下几点:为什么需要copy模块 ?
有了copy为什么需要deepcoy ,即与copy的差异?
如何自己实现一个copy方法?
如何自己实现一个deepcoy方法?
实例化的对象是可变对象还是不可变对象?
不论浅拷贝还是深拷贝,不可变对象内存地址与拷贝对象的内存地址一样,而可变对象内存地址与拷贝对象的内存地址不一样,这句话错在哪,举例说明?
该模块不复制模块、方法、栈追踪(stack trace)、栈帧(stack frame)、文件、套接字、窗口、数组以及任何类似的类型。它通过不改变地返回原始对象来(浅层或深层地)“复制”函数和类;这与
通过阅读正文可以了解以上问题
预备知识1- 不可变对象与可变对象
不可变对象,不可以修改对象,使用id()方法获取对象的内存地址,a=b=2,id(a)与id(b)是一样的,当操作 a+=1时 a=3 ,而b=2,2这个对象是没有发生改变的,注意是对象而不是变量;不可变对对象的类型有,常见的有int,str,bool,float,None,tuple,bytes,frozenset。
可变对象,对象可以修改,此时内存地址不变,比如testone=[1,2],id(test_one)=140474965482144,testone.append(3),id(test_one)内存地址没发生变化,常见的可变对象有 list,dict,set,bytearray。
可变对象与不可变对象是非常基础与重要的概念,是一定需要理解的。
正文
我们可以想到,开发中可定会有需要使用到副本的时候,这时可以自己实现复制的方法,python中有很多对象,每一个开发者都去针对每一种对象实现一个复制函数,极大的增加了工作量,同时每一个开发者写法不一,没有统一。那可不可以封装一个函数,供开发者直接调用即可?
于是一个复制函数呼之欲出,则需求出现了。这个时候需要考虑不同对象类型的复制情况:
针对不可变对象,则定义复制函数为:
def copy_immutable(x):
return x
针对可变对象,不同对象类型的复制方法不一:
def copy_of_list(x):
y = []
for i in x:
y.append(i)
return y
def copy_of_set(x):
y = set()
for i in x:
y.add(i)
return y
def copy_of_dict(x):
y = {}
for k,v in x:
y[k]=v
return y
复制的方法定义好了,我们希望做进一步优化,包装一个函数,可以判断输入对象的类型,然后获取对应的复制方法,这样就可以有统一的接口了,代码如下:
# 定义不可变对象的类型清单,这里用的元组而非列表,这有什么好处了?
# 当前模块定义的全局使用的可变对象元组 相比使用列表或者集合有什么好处了?
# 元组有哪些好处了?
immutable_object_tuple = (type(None), int, float, bool, complex, str, tuple,bytes, frozenset)
# 定义复制解析字典,通过对象类型获取对应的复制方法
copy_dispatch =d={}
for t in immutable_object_tuple:
d[t]= copy_immutable
d[list]=copy_of_list
d[set]=copy_of_set
d[dict]=copy_of_dict
# 定义统一的复制函数,通过类型自动获取对应的复制方法
def copy_func_version_one(x):
cls = type(x) # 获取对象类型
copy_method = copy_dispatch[cls] # 假设解析方法已经包含了所有的类型,实际是没有了,后续再优化
return copy_method(x)
这个时候初步的复制函数出来了,可以类比为内建copy方法的雏形,在使用时可以满足基本的复制需求,但是如果可变对象嵌套可变对象了?用列表举例如下:
nest_one = [4, 5]
test_list_one = [1, 2, 3, nest_one]
copy_of_test_list_one = copy_of_list(test_list_one)
print(id(test_list_one))
print(id(copy_of_test_list_one))
# 139840569478192
# 139840540438032
# 可见复制后生成了 新的对象
print(id(copy_of_test_list_one[-1]))
print(id(test_list_one[-1]))
#139840569475632
#139840569475632
# 复制后的嵌套可变对象是同一个内存地址,是同一个对象,共用的test_one
总结:
我们发现内嵌的可变对象复制的时候没有生成的新的对象,还是以前的对象,当其中一个嵌套可变对象发生改变时,另外一个也会发生变化,复制时候感觉只复制了一层,我们叫这个为浅拷贝Shallow copy,那如何实现一个深拷贝了(deepcopy)? 下一节介绍
补充知识: 列表复制的多种方法
# python 内建模块copy
import copy
L1 = [1, 2, [3]]
L2 = L1
L3 = L1[:]
L4 = list(L1)
L5 = copy.copy(L1)
L6 = copy.deepcopy(L1)
if __name__ == '__main__':
print('L1 id is :{}'.format(id(L1)))
print('L1[2] id is :{}'.format(id(L1[2])))
print('L3 id is :{}'.format(id(L3)))
print('L3[2] id is :{}'.format(id(L3[2])))
print('L4 id is :{}'.format(id(L4)))
print('L4[2] id is :{}'.format(id(L4[2])))
print('L5 id is :{}'.format(id(L5)))
print('L5[2] id is :{}'.format(id(L5[2])))
print('L6 id is :{}'.format(id(L6)))
print('L6[2] id is :{}'.format(id(L6[2])))
# 输出
L1 id is :2148361819400
L1[2] id is :2148361819208
L3 id is :2148361820680
L3[2] id is :2148361819208
L4 id is :2148360362312
L4[2] id is :2148361819208
L5 id is :2148361820616
L5[2] id is :2148361819208
L6 id is :2148361820552
L6[2] id is :2148361820488
# 总结 可见几种复制方法中出了deepcopy,其他
# 都是浅复制,尤其注意list()方法的复制为浅复
#制