# copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。 **只是简单的指针赋值
# copy.deepcopy 深拷贝 拷贝对象及其子对象 **指针赋值,且内容拷贝
用一个简单的例子说明如下:
>>>import copy >>>a = [1, 2, 3, 4, ['a', 'b', 'c']] >>> b = a >>> c = copy.copy(a) >>> d = copy.deepcopy(a)
很容易理解:
a是一个列表,表内元素a[4]也是一个列表(也就是一个内部子对象);
b是对a列表的又一个引用,所以a、b是完全相同的,可以通过id(a)==id(b)证明。
第4行copy.copy()是浅拷贝;
第5行copy.deepcopy()是深拷贝,通过id(c)和id(d)可以发现他们不相同,且与id(a)都不相同;
>>> id(a) 19276104 >>> id(b) 19276104 >>> id(c) 19113304 >>> id(d) 19286976
至于如何看深/浅拷贝的区别,可以通过下面的操作来展现:
>>> a.append(5) #操作1 >>> a[4].append('hello') #操作2
这时再查看结果:
>>> a [1, 2, 0, 4, ['a', 'b', 'c', 'hello'], 5] >>> b [1, 2, 0, 4, ['a', 'b', 'c', 'hello'], 5] >>> c [1, 2, 3, 4, ['a', 'b', 'c', 'hello']] >>> d [1, 2, 3, 4, ['a', 'b', 'c']]
可以发现:
a、b受了操作1、2的影响;
c只受操作2影响;
d不受影响;
a、b结果相同很好理解。
由于c是a的浅拷贝,只拷贝了父对象,因此a的子对象( ['a', 'b', 'c', 'hello'])改变时会影响到c;
d是深拷贝,完全不受a的影响;
浅拷贝是指拷贝的只是原对象元素的引用,换句话说,浅拷贝产生的对象本身是新的,但是它的内容不是新的,只是对原对象的一个引用。这里有个例子
>>> L1=[[1, 2], 3, 4] >>> L2 = L1[:] #利用切片完成一次浅拷贝 >>> id(L1) 3084416588L >>> id(L2) 3084418156L >>> L1[0][0] = 5 >>> print(L1) [[5, 2], 3, 4] >>> print(L2) [[5, 2], 3, 4]
可以看到,浅拷贝生产了一个新的对象列表L2,但是列表L2的内容确实对L1的引用,所以但改变L1中值的时候,L2的值也跟着变化了。
但是有点需要特别提醒的,如果对象本身是不可变的,那么浅拷贝时也会产生两个值,见这个例子:
>>> L1 = [1, 2] >>> L2 = L1[:] >>>print((L2) [1, 2] >>> print(L1) [1, 2] >>> L1[1]=111 >>> print(L1) [1, 111] >>> print(L2) [1, 2]
为什么L2的第二个元素没有变成111呢?因为数字在python中是不可变类型!!
这个顺便回顾下Python标准类型的分类:
# 可变类型: 列表,字典 # 不可变类型:数字,字符串,元组
理解了浅拷贝,深拷贝是什么自然就很清楚了。
python中有一个模块copy,deepcopy函数用于深拷贝,copy函数用于浅拷贝。
最后,对象的赋值是深拷贝还是浅拷贝?
对象赋值实际上是简单的对象引用
>>> a = 1 >>> id(a) 135720760 >>> b = a >>> id(b) 135720760
a和b完全是一回事。
详细请看下文:
Python中 copy, deepcopy 的区别及原因
虽然第一次听说 Python 中的 copy 与 deep copy 是作为 copy
模块中的两个 method。但它们其实是 OOP 语言中常见的概念。这里只说 Python,其他语言不了解。
Python 的 copy 模块中的
copy()
method 其实是与 deep copy 相对的 shallow copy。copy.copy(object)
就等于是对 object 做了 shallow copy。
先说结论:
- 对于简单的 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
- 复杂的 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 并没有变。
似乎 deep copy 更加符合我们对「复制」的直觉定义: 一旦复制出来了,就应该是独立的了。如果我们想要的是一个字面意义的「copy」,那就直接用 deep_copy
即可。
那么为什么会有 shallow copy 这样的「假」 copy 存在呢? 这就是有意思的地方了。
Python 与众不同的变量储存方法
Python 存储变量的方法跟其他 OOP 语言不同。它与其说是把值赋给变量,不如说是给变量建立了一个到具体值的 reference。
当在 Python 中 a = something
应该理解为给 something 贴上了一个标签 a。当再赋值给 a
的时候,就好象把 a 这个标签从原来的 something 上拿下来,贴到其他对象上,建立新的 reference。 这就解释了一些 Python 中可能遇到的诡异情况:
>>> 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
新的值(从 [1, 2, 3]
变为 [4, 5, 6]
);而第二段则是把 list 中每个元素分别改变。
而对 b
的影响则是不同的,一个没有让 b
的值发生改变,另一个变了。怎么用上边的道理来解释这个诡异的不同呢?
首次把 [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 产生疑惑,是有一次想对一个复杂的 list 遍历并且做修改。
这种情况下,最好先建立一个 copy 出来:
If you need to modify the sequence you are iterating over while inside the loop (for example to duplicate selected items), it is recommended that you first make a copy. Iterating over a sequence does not implicitly make a copy.
– Python Documentation
于是想当然用了 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 到底。
– Python Doctumentation
这个解释看上去略抽象。
先看这里的 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了。
既然完全独立,那无论如何改变其中一个,另一个自然不会随之改变。