当前位置: 首页 > 后端技术 > Python

学习Python一年,这次终于弄懂了浅拷贝和深拷贝

时间:2023-03-25 20:45:25 Python

学习Python一年了,这次终于明白了浅拷贝和深拷贝。很多文章看起来都是一知半解,所以我决定用自己的理解来写这样一篇文章。当有人提到Python中的复制操作时,你会不会立马站起来说:“我会”,于是你就有了如下操作:importcopyx=copy.copy(y)#ShallowcopyIknowx=copy.deepcopy(y)#我是来深拷贝的。浅拷贝和深拷贝有什么区别?你能告诉我吗?从referencevs.copy入手首先,我们要搞清楚什么是对象引用和对象拷贝(replication)。对象引用Python中对对象的赋值其实就是对对象的引用。在创建对象并将其赋值给另一个变量时,Python不会复制对象,而只会复制对象的引用。>>>a=1>>>b=a>>>id(a)==id(b)True>>>x=[1,2,3]>>>y=[x,4]>>>>x[1,2,3]>>>y[[1,2,3],4]>>>>>>>>id(x)==id(y)False>>>id(x)==id(y[0])True如果这个过程不明白,可以看下图:当我们对x列表进行操作时,会发现y中发生了意想不到的事情:>>>x[1]=2020>>>y[[1,2020,3],4]由于列表是可变的,当列表对象x被修改时,对象y中对x的引用也会改变。因此,重要的是,当我们就地修改可变对象时,它可能会影响程序中其他地方对同一对象的其他引用。如果不想这样做,则需要明确告诉Python复制对象。对象复制如果需要复制,可以进行以下操作:无限制切片表达式(L[:])工厂函数(如list/dir/set)字典复制方法(X.copy())复制标准库模块(importcopy)比如假设有一个列表L和一个字典D:>>>L=[2019,2020,2021]>>>D={'1':2019,'2':2020,'3':2021}>>>>>>A=L[:]#区分A=L或A=List(L)>>>B=D.copy()#区分B=D>>>A[2019,2020,2021]>>>B{'1':2019,'2':2020,'3':2021}这样定义之后,当你修改A和B时,会发现原来的L和B没有变化D产生影响,因为,这是对象的副本。>>>A[1]='快乐'>>>B[3]='今天'>>>L,D([2019,2020,2021],{'1':2019,'2':2020,'3':2021})>>>A,B([2019,'快乐',2021],{'1':2019,'2':2020,'3':2021,3:'今天'})上面的列表和字典的拷贝操作默认是浅拷贝:字典的浅拷贝可以使用dict.copy()方法来完成,列表的浅拷贝可以通过分配整个列表的一部分来完成列表,例如,copied_list=original_list[:]。说到这里,问题来了?什么是浅拷贝?浅拷贝对应的深拷贝怎么解释?说说浅拷贝和深拷贝官方文档定义:浅拷贝和深拷贝的区别只与复合对象有关(即包含其他对象的对象,比如列表或类的实例):一个浅拷贝构造一个新的,然后(尽可能)将在原始对象中找到的引用插入其中。深拷贝构造一个新的复合对象,然后递归地插入在原始对象中找到的对象的副本。浅拷贝浅拷贝:复制最外层的对象本身,而内部的元素只是复制一个引用。也就是把对象再复制一遍,但是我不复制对象中引用的其他对象。通俗地说就是:有(篮子)?(鸡蛋)在你的柜子(对象)中,然后进行浅拷贝。我只复制了最外面的柜子,至于里面的元素(?和?)我没有复制。当我们遇到简单的对象时,上面的解释似乎很容易理解;如果我们遇到复合对象,比如下面的代码:l1=[3,[66,55,44],(3,7,21)]l2=list(l1)l1.append(100)print('l1:',l1)print('l2:',l2)l1[1].remove(55)l2[1]+=[33,22]l2[2]+=(9,9,81)print('l1:',l1)print('l2:',l2)代码解释:l2是l1的浅拷贝向l1追加100,对l2没有影响l1的内部列表l1[1中删除55也有影响l2,因为l1[1]和l2[1]绑定到同一个列表。对于变量对象,l2[1引用的列表是+=就地修改列表。此修改会导致l1[1]也发生变化。对于元组,+=运算符创建一个新的元组,然后将其重新绑定到变量l2[2]。这相当于l2[2]=l2[2]+(10,11)。现在,l1和l2中最后位置的元组不是同一个对象。这段代码可视化如下:自己试一下,深拷贝可以点这里深拷贝:无论是外围元素还是内部元素,都是拷贝对象本身,而不是引用。也就是把这个对象再复制一遍,我把这个对象中引用的其他对象也复制了一遍。比较上面的篮子和鸡蛋:你的橱柜(对象)包含一个?(篮子)?(鸡蛋),然后深抄的意思。复制最外面的橱柜和里面的内部元素(?和?)。fromcopyimportdeepcopyl1=[3,[66,55,44],(3,7,21)]l2=deepcopy(l1)l1.append(100)print('l1:',l1)print('l2:',l2)l1[1].remove(55)l2[1]+=[33,22]l2[2]+=(9,9,81)print('l1:',l1)print('l2:',l2)输出结果:不可变类型(如数字、字符串等'原子'类型)的对象对深拷贝和浅拷贝没有影响,最终地址值和值相等。即“obj是copy.copy(obj)”,“obj是copy.deepcopy(obj)”变量类型objects=浅拷贝:等值,等地址拷贝浅拷贝:等值,不等地址deepcopy深拷贝:等value,unequaladdressObjectswithcircularreference如果对象有循环引用,那么这个朴素的算法就会进入死循环。deepcopy函数会记住已经复制的对象,因此可以优雅地处理循环引用。循环引用:b引用a,然后追加到a;deepcopy会想办法复制a,copy会进入死循环。比如下面的代码:fromcopyimportdeepcopy,copya=[80,90]b=[a,100]a.append(b)print("a:",a)print("b:",b)c=deepcopy(a)print("c:",c)d=copy(b)print("d:",d)输出结果:a:[80,90,[[...],100]]b:[[80,90,[...]],100]c:[80,90,[[...],100]]d:[[80,90,[[...],100]],100]深浅拷贝的功能减少了内存的使用。清理、修改或存储数据后,复制原始数据,防止修改数据后找不到原始数据。可以通过实现__copy()和__deep__()方法来自定义和控制复制行为。总结看完这篇文章,转身对你的同桌说:“x同学,听说你最近在学Python,你知道浅拷贝和深拷贝吗?”告诉你:“复制其实就是我们用过但可能不知道的前几个操作语句(前3个),而浅拷贝是Python默认的复制方式。复制的方式如下:变量类型切片操作:[:]工厂函数(如list/dir/set)字典拷贝方法(X.copy())然后Python有一个专门的拷贝标准库模块:包含两个方法copy()和deepcopy()的浅拷贝是比如我只复制最外层的对象,并没有复制对象中引用的其他对象。深复制就是把对象和对象中的内容完全复制。复制的目的:节省内存,防止数据丢失。后记:深浅拷贝的坑和难点只在复合对象上,简单对象就是我们通常理解的拷贝,对于非容器类型(比如数字、字符串等'原子'类型的对象)不拷贝.如果你的同桌还是不明白,你可以扔是文章给他,让他仔细阅读。如果你觉得这篇文章还不错,请点个赞或者收藏一下,关注一下会更好。本文内容供参考:中文网:Visualizeyourcoderunning《流畅的Python》--Chapter8ObjectReferences,MutabilityandGarbageCollection《Python3标准库》--2.9copy—DuplicateObjects