您的位置:首页 > 编程语言 > Python开发

拷贝Python对象:深拷贝与浅拷贝

2017-02-04 11:03 253 查看
[Python][6]中的拷贝概念与[C++][6]中的一样。也即深拷贝就是对对象资源的拷贝,浅拷贝就是对引用的拷贝。这与我们直觉中的拷贝有点不一样,所以在实际应用中容易搞混。

一、熟悉python内存管理

在python中,变量名不用事先声明,变量类型也不用事先声明,变量会在第一次赋值时自动声明,在创建时,也就是赋值的时候,解释器会根据语法和右侧的操作数来决定新对象的类型。

要保持追踪内存中的对象,Python使用引用计数这一简单技术。也就是说python内部记录着所有使用中的对象各有多少引用。当对象被创建时,就创建一个引用计数,并且被设置为1(事实上它并不是1,可能是python本身对创建的对象有引用)。

>>> x = 123 #新创建的整型对象123赋值给x,其引用计数为1
>>> y = x #y是x的别名,现在整型对象123的引用计数为2


二. Python 的复制

先看代码:

>>> a = [1,2,3]
>>> b = a
>>> b.append(111)
>>> print(a,b)
>>> [1,2,3,111] [1,2,3,111]
>>>print(id(a),id(b))
>>> 64880112 64880112


从上面可见,对象的赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。

如果你想修改一个对象,而且想让原始的对象不受影响,那你就需要对象复制。可以使用如下几个方法:

(1)、使用切片[:]操作进行拷贝

(2)、使用工厂函数(如list/dir/set)等进行拷贝

(3)、copy.copy()(需导入copy模块)

>>> person = ["name",["save",100]]
>>> tom = person[:]
>>> jack = list(person)
>>> tom
['name', ['save', 100]]
>>> jack
['name', ['save', 100]]
>>> [id(x) for x in (person,tom,jack)]
[52590472, 63910176, 67064136]


在上面的代码中,我们采用切片和工厂函数list进行拷贝,可以看到拷贝后的tom,jack,person的id值均不同,那这是否是已经达到我们想要的拷贝呢?

现在我们对拷贝的tom,jack,进行一些操作:

>>> tom[0] = "tom"
>>> jack[0] = "jack"
>>> tom
['tom', ['save', 100]]
>>> jack
['jack', ['save', 100]]
>>> tom[1][1] = 50
>>> tom
['tom', ['save', 50]]
>>> jack
['jack', ['save', 50]]


上面的实例中,我们成功修改 了姓名,但是对存款的修改却没有达到预想的效果(前提是我们希望复制后的tom,jack 互不影响)。

原因是我们只做了浅拷贝。对一个对象进行浅拷贝其实是新创建了一个类型跟原对象一样,其内容是原对象元素的引用,换句话说,这个拷贝对象本身是新的,但是它的内容不是。

那么为什么修改姓名时没有互相影响,而修改存款时会互相影响?这是因为在这两个列表对象中,第一个对象是不可变对象(是个字符串类型),第二个对象是可变对象(一个列表)。

在python中字符串不可以修改,所以在为tom和jack重新命名的时候,会重新创建一个’tom’和“jack”对象,替换旧的’name’对象。这就说明了,浅复制(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用.

>>> import copy
>>> aa = [1,2,3]
>>> bb =copy.copy(aa)
>>> id(aa)
63911536
>>> id(bb)
67064176
>>> bb[0] = 100
>>> aa
[1, 2, 3]
>>> bb #由于数字不可变,修改的时候会替换旧的对象
[100, 2, 3]


下面试试复制的对象中包含可变对象:

>>> lis = [[1],["aaa"]]
>>> clis = copy.copy(lis)
>>> lis
[[1], ['aaa']]
>>> clis
[[1], ['aaa']]
>>> clis[0].append("bbbb")
>>> lis
[[1, 'bbbb'], ['aaa']]
>>> clis
[[1, 'bbbb'], ['aaa']]


上面的示例中,复制后的clis的修改影响到了原来的lis,这并不是我们想要的。如果希望复制一个容器对象,以及它里面的所有元素(包含元素的子元素),使用copy.deepcopy,这个方法会消耗一些时间和空间,不过,如果你需要完全复制,这是唯一的方法.

#深拷贝
>>> deeplis = copy.deepcopy(lis)
>>> deeplis
[[1, 'bbbb'], ['aaa']]
>>> deeplis[0].append("aaaa")
>>> deeplis
[[1, 'bbbb', 'aaaa'], ['aaa']]
>>> lis
[[1, 'bbbb'], ['aaa']]


注意:

1、对于非容器类型(如数字、字符串、和其他‘原子’类型的对象)没有被拷贝一说。

2、如果元祖变量只包含原子类型对象,则不能深copy。

参考:http://www.cnblogs.com/BeginMan/p/3197649.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: