一样是一篇个人梳理,因为这个东西被无数人研究过了
推荐文章:
图解Python深拷贝和浅拷贝
另外这篇的深拷贝部分也有点意思。
你真得理解 python 的浅拷贝和深拷贝吗?
下面大部分内容是基于另外一篇博客,源不可考。
直接赋值
首先,如果我们不进行拷贝,而是直接赋值,很有可能会出现意料之外的结果。比如a是一个列表,b=a,那么修改a的同时,b也会同样被修改,因为Python对象的赋值都是进行引用(内存地址)传递的,实际上a和b指向的都是同一个对象。
>>> a = [1,2,3]
>>> b = a
>>> a[2] = 4
>>> a
[1, 2, 4]
>>> b
[1, 2, 4]
>>> a is b
True
浅拷贝
为了避免这种情况发生,我们可以使用浅拷贝:
>>> a = [1,2,3]
>>> import copy
>>> b = copy.copy(a)
>>> b is a
False
>>> a[0] is b[0]
True
浅拷贝会创建一个新的对象,然后把生成的新对象赋值给新变量。注意这句话的意思,上面这个例子中1,2,3这三个int型对象并没有创建新的,新的对象是指copy创建了一个新的列表对象,这样a和b这两个变量指向的列表对象就不是同一个,但和两个列表对象里面的元素依然是按引用传递的,所以a列表中的对象1和b列表中的对象1是同一个。 但是这时修改a列表的不可变对象,b列表不会受到影响:
>>> a[0] = 4
>>> a
[4, 2, 3]
>>> b
[1, 2, 3]
由于浅拷贝时,对于对象中的元素,浅拷贝只会使用原始元素的引用(内存地址),所以如果对象中的元素是可变对象,浅拷贝就没辙了。比方说列表中包含一个列表,这时改动a,浅拷贝的b依然可能受影响:
>>> a = [1,2,[3,]]
>>> b = copy.copy(a)
>>> a is b
False
>>> a[2].append(4)
>>> a
[1, 2, [3, 4]]
>>> b
[1, 2, [3, 4]]
可以产生浅拷贝的操作有以下几种:
- 使用切片[:]操作
- 使用工厂函数(如list/dir/set)
工厂函数看上去像函数,实质上是类,调用时实际上是生成了该类型的一个实例,就像工厂生产货物一样.
- 使用copy模块中的copy()函数
深拷贝
对于这个问题,又引入了深拷贝机制,这时不仅创建了新的对象,连对象中的元素都是新的,深拷贝都会重新生成一份,而不是简单的使用原始元素的引用(内存地址)。注意了,对象中的元素,不可变对象还是使用引用,因为没有重新生成的必要,变量改动时会自动生成另一个不可变对象,然后改变引用的地址。但可变对象的内容是可变的,改动后不会产生新的对象,也不会改变引用地址,所以需要重新生成。
>>> a = [1,2,[3,]]
>>> b = copy.deepcopy(a)
>>> a is b
False
>>> a[0] is b[0]
True
>>> a[2] is b[2]
False
这时再改变a中的元素对b就完全没有影响了:
[1, 2, [3]]
>>> a = [1,2,[3,]]
>>> b = copy.deepcopy(a)
>>> a[2].append(4)
>>> a
[1, 2, [3, 4]]
>>> b
[1, 2, [3]]
特殊情况
- 对于非容器类型(如数字、字符串、和其他'原子'类型的对象)是没有拷贝这个说法的。
>>> a = 'hello'
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is b
True
>>> a is c
True
对于这种类型的对象,无论是浅拷贝还是深拷贝都不会创建新的对象。
- 如果元祖变量只包含原子类型对象,则不能深拷贝:
原子类型指所有的数值类型以及字符串
>>> a=(1,2,3)
>>> a = (1,2,3)
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is b
True
>>> a is c
True
元组本身是不可变对象,如果元组里的元素也是不可变对象,就没有进行拷贝的必要了。实测如果元组里面的元素是只包含原子类型对象的元组,则也属于这个范畴。
>>> a = (1,2,(3,))
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)
>>> a is c
True
>>> a is b
True
总结
Python中对象的赋值都是进行对象引用(内存地址)传递
使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
非容器类型(如数字、字符串、和其他'原子'类型的对象)不存在拷贝。
只包含原子类型对象的元组变量不存在拷贝。
基于以上讨论,我们可以轻松理解下面的现象。
函数传参
a=1
def func(a):
a=2
func(a)
print a # 1
a=[]
def func(a):
a.append(1)
func(a)
print a # [1]
字典浅拷贝
a = []
for i in xrange(5):
a.append({'num': i})
print a # [{'num':0},...,{'num':4}]
a = []
d = {'num': 0}
for i in xrange(5):
d['num'] = i
a.append(d)
print a # [{'num':4},...,{'num':4}]