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

[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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值