码迷,mamicode.com
首页 > 编程语言 > 详细

Python--浅拷贝和深拷贝

时间:2016-08-03 12:03:26      阅读:213      评论:0      收藏:0      [点我收藏+]

标签:

之前,我在博文Python–内存管理中说明了Python中对象赋值的问题,我们已经知道,当创建一个对象,并且把这个对象赋值给另一个变量的时候,其实并没有拷贝这个对象,而只是给这个对象增加了一个引用(这一点具体可以参见链接给出的博文中“引用计数”这一节)

赋值是引用而非拷贝

复习一下,比如下面的例子

a = 2
b = a
print(id(a), id(b)) # >>> 4443297952 4443297952

# 对于列表这种可变类型,也是一样的
c = [1, 2, "we"]
d = c
print(id(c), id(d)) # >>> 4362693768 4362693768

正因为没有真正意义上拷贝,所以“一变俱变”

c = [1, 2, "we"]
d = c
d[1] = 3
print(d) # >>> [1, 3, "we"]
print(c) # >>> [1, 3, "we"]

浅拷贝

1. 三种拷贝方法

但是这显然是不能满足满足实际要求的,所以,我们需要相应的拷贝方法,就以列表为例,拷贝方法有三种

# 切片[:]
a = ["we", 1, 2]
b = a[:]
b[0] = "you"
print(b, id(b)) # >>> [‘you‘, 1, 2] 4427664520
print(a, id(a)) # >>> [‘we‘, 1, 2] 4427664648

# copy()函数
a1 = ["we", 1, 2]
b1 = a1.copy()
b1[1] = 3
print(b1, id(b1)) # >>> [‘we‘, 3, 2] 4537855112
print(a1, id(a1)) # >>> [‘we‘, 1, 2] 4537855240

# 工厂函数,比如列表的list(), 字典的dict()等
a2 = ["we", 1, 2]
b2 = list(a2)
b2[2] = 3
print(b2, id(b2)) # >>> [‘we‘, 1, 3] 4507116488
print(a2, id(a2)) # >>> [‘we‘, 1, 2] 4507081864

从以上三个例子可以看出,三种拷贝方法都达到了相同的效果,拷贝生成了一个新的对象,对新对象做更改,将不影响原对象。但是,需要注意的是,我们这个例子里面列表的元素可都是原子型的(字符串,数字等等),如果是可变类型的,情况就不一定了。

2. 拷贝对象,但未拷贝对象的内容

还是上面的例子,让我们再深入一步

a = [1, 2, 3]
b = a.copy()
print([id(x) for x in a]) # >>> [4412074112, 4412074144, 4412074176]
print([id(x) for x in b]) # >>> [4412074112, 4412074144, 4412074176]

不难发现,实际上b虽然是a的拷贝,但是b的内容却全都是a的内容的引用,并未拷贝。

我们将这种新建了对象,但是对象的内容q是却是原对象的引用的拷贝,称之为浅拷贝。

但是,如果我们重新对新对象的元素更改时,如果这个元素是原子型的,比如数字,字符串,元组。那么其实是新建了个对象,然后令这个别名,引用这个新对象。所以,看上去好像是真的对对象的内容也拷贝了似的,但其实没有。话说的有点绕,看下面这个例子,也许会清楚些。

a = [1, 2, 3]
b = a.copy()

# 根据上面的例子可知,此时a与b的每个元素的id一定是一样的
print([id(x) for x in a]) # >>> [4339624064, 4339624096, 4339624128]

# 新建了对象(整数0),并令b[0]这个名字引用这个对象
b[0] = 0 

# 此时,b[0]的地址就变了
print([id(x) for x in b]) # >>> [4339624032, 4339624096, 4339624128]

# 但是,a却没有改变
print([id(x) for x in a]) # >>> [4339624064, 4339624096, 4339624128]

我想,上面的讲解已经说明了为什么类似于切片,copy() 函数,工厂函数这些拷贝方法看上去确实是新建了对象内容(因为改变拷贝对象的内容,原对象未受影响),但是他的机制依然是浅拷贝,拷贝对象的内容依然是原对象相应内容的引用。

知道这个有什么用呢?比如当d中对象包含列表这种情况,我们就不会认为对拷贝对象的列表元素的改变会保证原对象不变。

a = [‘I‘, [1, 2]]
b = a.copy()
b[1][0] = 0
print(b) # >>> [‘I‘, [0, 2]]

# 这里特别小心,b的内容变了,而且因为b的内容只是对a中相应内容的引用,所以a也变了。
print(a) # >>> [‘I‘, [0, 2]]

深拷贝

那么,当然会遇到需要在拷贝对象的同时,也将对象的所有内容拷贝的场景。也就是说一种从内而外,从小到大的全部拷贝。我们将这种“彻底”的拷贝,称为“深拷贝”。

深拷贝的方法也简单,引入copy库,用到 copy.deepcopy() 函数即可

import copy
a = [‘I‘, [1, 2]]
b = copy.deepcopy(a)
print([id(x) for x in a]) # >>> [4380781288, 4381566152]
print([id(x) for x in b]) # >>> [4380781288, 4381565384]

需要注意的是,对于对象的内容是原子型的,比如上面例子中,列表的第一个元素是字符串,则不存在深拷贝的问题,一概使用浅拷贝。

其实,这当然是完全不会影响我们应用的,因为原子型的对象,我们进行修改时,会重新新建对象,然后引用,这个上面已经说过了。比如

import copy
a = [‘I‘, [1, 2]]
b = copy.deepcopy(a)
b[0] = "You"
b[1][1] = 3

# 随意改变b,a完全不受影响
print(b) # >>> [‘You‘, [1, 3]]
print(a) # >>> [‘I‘, [1, 2]]

Python--浅拷贝和深拷贝

标签:

原文地址:http://blog.csdn.net/guoziqing506/article/details/52083094

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!