当前位置: 首页 > 科技观察

Python列表深-浅拷贝原理解密

时间:2023-03-12 20:36:39 科技观察

1.python列表的深/浅拷贝Python有一个常用的数据类型:list。在使用list的时候,往往需要考虑一件事,那就是:浅拷贝和深拷贝。至于什么是深拷贝和浅拷贝,先从一个示例代码来分析:importcopy#listsourcedatausedinthetestlists=[[1,2,3],4,5,6]deflow_copy():#list浅拷贝low_list=copy.copy(lists)returnlist(low_list)defdeep_copy():#list深拷贝deep_list=copy.deepcopy(lists)returnlist(deep_list)if__name__=="__main__":print("sourcelist:",lists)#分别获取浅拷贝、深拷贝列表对象=====================")#将值7追加到源数据的第0条数据print("将值7追加到源数据的第0条数据list,start")lists[0].append(7)print("将值7附加到源列表的第0条数据,end")print("===========================")#源数据的第0条数据加值7后校验,深拷贝数据的变化print("sourcelist:",lists)print("浅拷贝列表:",lists_c)print("深拷贝列表:",lists_d)#执行结果##源列表:[[1,2,3],4,5,6]#浅拷贝列表:[[1,2,3],4,5,6]#深拷贝列表:[[1,2,3],4,5,6]#==========================#对于源列表的第0个给数据加一个值7,start#给source列表的第0条数据加一个值7,end#===================================#sourcelist:[[1,2,3,7],4,5,6]#shallowcopylist:[[1,2,3,7],4,5,6]#深拷贝列表:[[1,2,3],4,5,6]从示例代码可以看出:经过list的浅拷贝和深拷贝后,修改源数据会直接影响到浅拷贝的数据,而深拷贝的数据将直接受到影响。复制的数据不受影响这说明了什么,又是如何实现的?2、pyhtonlist的实现首先要说明几点:python的底层源码是用C语言实现的。都是对象)Python对象大致可以分为以下几种:参考https://flaggo.github.io/python3-source-code-analysis/objects/object/Fundamentalobject:TypeobjectNumericobject:NumericobjectSequenceobject:容纳其他对象的序列集合对象Mappingobject:类似于C++中map的关联对象Internalobject:Python虚拟机在运行时内部使用的对象3.listobject在python源码实现中,list的结构如下:/源文件:include/listobject.h//listobject.htypedefstruct{//对象的公共头PyObject_VAR_HEAD//指向列表元素的指针向量,list[0]为ob_item[0]//可以看到ob_item是一个secondaryPointer//也就是说,**ob_item表示它是一个指向PyObject类型指针数组的指针//*ob_item表示它是一个PyObject类型指针数组/*Vectorofpointerstolistelements.list[0]isob_item[0],etc.*/PyObject**ob_item;/*ob_itemcontainsspacefor'allocated'elements.Thenumber*currentlyinuseisob_size.*Invariants:*0<=ob_size<=allocated*len(list)==ob_size*ob_item==NULLimpliesob_size==allocated==0*list.sort()临时设置allocated为-1到检测突变。**项目通常不能为NULL,除非在构造期间*thelistisnotyetvisibleoutsidethefunctionthatbuildsit.*///列表包含元素的总数Py_ssize_tallocated;}PyListObject;从list的结构可以看出ob_item字段实际存放的是object,是指向指针数组的指针,所以我们知道PyListObject结构体是一个多级结构体创建列表的过程主要分为两步:创建一个PyListObject结构体并初始化ob_item指向的指针数组//源文件位置:Objects/listobject.c//新建一个listPyObject*PyList_New(Py_ssize_tsize){//判断列表的大小是否合法if(size<0){PyErr_BadInternalCall();returnNULL;}struct_Py_list_state*state=get_list_state();//最终创建的列表对象指针PyListObject*op;#ifdefPy_DEBUG//PyList_New()mustnotbecalledafter_PyList_Fini()assert(state->numfree!=-1);#endifif(state->numfree){state->numfree--;op=state->free_list[state->numfree];_Py_NewReference((PyObject*)op);}else{//新建一个listop=PyObject_GC_New(PyListObject,&PyList_Type);if(op==NULL){returnNULL;}}if(size<=0){op->ob_item=NULL;}else{op->ob_item=(PyObject**)PyMem_Calloc(size,sizeof(PyObject*));if(op->ob_item==NULL){Py_DECREF(op);returnPyErr_NoMemory();}}Py_SET_SIZE(op,size);op->allocated=size;_PyObject_GC_TRACK(op);return(PyObject*)op;}4.listshallowcopy//源文件位置:Objects/listobject.c/*[clinicinput]list.copyReturnashallowcopyofthelist.[clinicstartgeneratedcode]*///列表的浅拷贝ihighPy_ssize_t的类型是当前系统中一个指针的大小;if(len<=0){returnPyList_New(0);}//生成一个新的listnp=(PyListObject*)list_new_prealloc(len);if(np==NULL)returnNULL;//从list的第一个位置开始a->ob_itemmoveilow,即:移动到第ilow个数值元素的指针位置src=a->ob_item+ilow;//新列表数值列表的第一个位置dest=np->ob_item;//copy,注意:只是复制了对象的指针for(i=0;iob_size=sizePy_SET_SIZE(np,len);return(PyObject*)np;}浅拷贝后,从内存布局的变化可以看出,新旧列表共享底层数据对象,这也是一个列表修改其他列表的原因被影响到的5、列表深拷贝深拷贝后,从内存布局的变化可以看出,新旧列表使用不同的底层数据对象,不会导致一个列表的改变影响到其他列表。总结通过分析python底层源码,我们可以了解链表的底层结构和深浅拷贝的原理。开发过程中是使用深拷贝还是浅拷贝需要根据实际情况来处理。浅拷贝在复制的时候只复制第一层的引用。如果元素是一个可变对象并且被修改,复制的对象也会改变。深拷贝时,会一层层拷贝,直到所有的引用都是不可变对象。Python有很多种实现浅拷贝的方法,copy模块的copy函数,对象的copy函数,工厂方法,slice等。大多数情况下,在写程序的时候,除非有特定的需要,都会用到浅拷贝。浅拷贝的优点:拷贝速度快,占用空间少,拷贝效率高。