今天在写代码的时候遇到一个奇葩的问题,问题描述以下:python
代码中声明了一个list,将list做为参数传入了function1()中,在function1()中对list进行了del()即删除了一个元素。socket
而function2()也把list做为参数传入使用,在调用完function1()以后再调用function2()就出现了问题,list中的值已经被改变了,就出现了bug。ide
直接上代码:oop
list = [0, 1, 2, 3, 4, 5] def function1(list): del list[1] print(list) def function2(list): print(list) function1(list) function2(list)
我并不但愿function2()中的list改变,查了一下解决办法说是可对list进行copy:spa
newList = list.copy()
function2(newList)
在查解决办法的过程当中发现了还有一个方法叫作deepcopy(),那么问题来了,deepcopy()与copy()的区别是什么?3d
先点到源码里看了下源码,发现有注释,很开心。注释以下:code
"""Generic (shallow and deep) copying operations. Interface summary: import copy x = copy.copy(y) # make a shallow copy of y x = copy.deepcopy(y) # make a deep copy of y For module specific errors, copy.Error is raised. The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances). - A shallow copy constructs a new compound object and then (to the extent possible) inserts *the same objects* into it that the original contains. - A deep copy constructs a new compound object and then, recursively, inserts *copies* into it of the objects found in the original. Two problems often exist with deep copy operations that don't exist with shallow copy operations: a) recursive objects (compound objects that, directly or indirectly, contain a reference to themselves) may cause a recursive loop b) because deep copy copies *everything* it may copy too much, e.g. administrative data structures that should be shared even between copies Python's deep copy operation avoids these problems by: a) keeping a table of objects already copied during the current copying pass b) letting user-defined classes override the copying operation or the set of components copied This version does not copy types like module, class, function, method, nor stack trace, stack frame, nor file, socket, window, nor array, nor any similar types. Classes can use the same interfaces to control copying that they use to control pickling: they can define methods called __getinitargs__(), __getstate__() and __setstate__(). See the documentation for module "pickle" for information on these methods. """
然而看了看,一脸懵逼。仍是百度继续查资料吧:component
https://iaman.actor/blog/2016/04/17/copy-in-python大佬总结的很好。orm
copy其实就是shallow copy,与之相对的是deep copy对象
结论:
1.对于简单的object,shallow copy和deep copy没什么区别
>>> import copy >>> origin = 1 >>> cop1 = copy.copy(origin) #cop1 是 origin 的shallow copy >>> cop2 = copy.deepcopy(origin) #cop2 是 origin 的 deep copy >>> origin = 2 >>> origin 2 >>> cop1 1 >>> cop2 1 #cop1 和 cop2 都不会随着 origin 改变本身的值 >>> cop1 == cop2 True >>> cop1 is cop2 True
2.复杂的 object, 如 list 中套着 list 的状况,shallow copy 中的子list,并未从原 object 真的「独立」出来。
若是你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一块儿变。这跟咱们直觉上对「复制」的理解不一样。
>>> import copy >>> origin = [1, 2, [3, 4]] #origin 里边有三个元素:1, 2,[3, 4] >>> cop1 = copy.copy(origin) >>> cop2 = copy.deepcopy(origin) >>> cop1 == cop2 True >>> cop1 is cop2 False #cop1 和 cop2 看上去相同,但已再也不是同一个object >>> origin[2][0] = "hey!" >>> origin [1, 2, ['hey!', 4]] >>> cop1 [1, 2, ['hey!', 4]] >>> cop2 [1, 2, [3, 4]] #把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2
cop1
,也就是shallow copy 跟着 origin 改变了。而 cop2
,也就是 deep copy 并无变。
那么问题又来了,有deepcopy直接用就行了为啥还要有copy?
这个问题的解决要从python变量存储的方法提及,在python中,与其说是把值赋给了变量,不如说是给变量创建了一个到具体值的reference(引用)
>>> a = [1, 2, 3] >>> b = a >>> a = [4, 5, 6] //赋新的值给 a >>> a [4, 5, 6] >>> b [1, 2, 3] # a 的值改变后,b 并无随着 a 变 >>> a = [1, 2, 3] >>> b = a >>> a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素 >>> a [4, 5, 6] >>> b [4, 5, 6] # a 的值改变后,b 随着 a 变了
上面代码,都改变了a的值,不一样的是:第一段是给a赋新值,第二段是直接改变了list中的元素。
下面解释下这诡异的现象:
首次把 [1, 2, 3]
当作一个物品。a = [1, 2, 3]
就至关于给这个物品上贴上 a
这个标签。而 b = a
就是给这个物品又贴上了一个 b
的标签。
a = [4, 5, 6]
就至关于把 a
标签从 [1 ,2, 3]
上撕下来,贴到了 [4, 5, 6]
上。
在这个过程当中,[1, 2, 3]
这个物品并无消失。 b
自始至终都好好的贴在 [1, 2, 3]
上,既然这个 reference 也没有改变过。 b
的值天然不变。
a[0], a[1], a[2] = 4, 5, 6
则是直接改变了 [1, 2, 3]
这个物品自己。把它内部的每一部分都从新改装了一下。内部改装完毕后,[1, 2, 3]
自己变成了 [4, 5, 6]
。
而在此过程中,a
和 b
都没有动,他们还贴在那个物品上。所以天然 a
b
的值都变成了 [4, 5, 6]
。
用copy.copy()
。结果却发现本体与 copy 之间并非独立的。有的时候改变其中一个,另外一个也会跟着改变。也就是本文一开头提到的例子:
>>> import copy >>> origin = [1, 2, [3, 4]] #origin 里边有三个元素:1, 2,[3, 4] >>> cop1 = copy.copy(origin) >>> cop2 = copy.deepcopy(origin) >>> cop1 == cop2 True >>> cop1 is cop2 False #cop1 和 cop2 看上去相同,但已再也不是同一个object >>> origin[2][0] = "hey!" >>> origin [1, 2, ['hey!', 4]] >>> cop1 [1, 2, ['hey!', 4]] >>> cop2 [1, 2, [3, 4]] #把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2
官方解释:
The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances): A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original. A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original. 两种 copy 只在面对复杂对象时有区别,所谓复杂对象,是指对象中含其余对象(如复杂的 list 和 class)。 由 shallow copy 创建的新复杂对象中,每一个子对象,都只是指向本身在原来本体中对应的子对象。而 deep copy 创建的复杂对象中,存储的则是本体中子对象的 copy,而且会层层如此 copy 到底。
先看这里的 shallow copy。 如图所示,cop1 就是给当时的 origin 创建了一个镜像。origin 当中的元素指向哪, cop1 中的元素就也指向哪。这就是官方 doc 中所说的 inserts references into it to the objects found in the original
。
这里的关键在于,origin[2]
,也就是 [3, 4] 这个 list。根据 shallow copy 的定义,在 cop1[2]
指向的是同一个 list [3, 4]。那么,若是这里咱们改变了这个 list,就会致使 origin 和 cop1 同时改变。这就是为何上边 origin[2][0] = "hey!"
以后,cop1 也随之变成了 [1, 2, ['hey!', 4]]
。
再来看 deep copy。 从图中能够看出,cop2 是把 origin 每层都 copy 了一份存储起来。这时候的 origin[2]
和 cop2[2]
虽然值都等于 [3, 4],但已经不是同一个 list了。
既然彻底独立,那不管如何改变其中一个,另外一个天然不会随之改变。