Python浅拷贝和深拷贝

对象拷贝的方法具有多种,例如简单的赋值、切片、工厂(list()、dict())等,不同的方法所拷贝的内容是不同的。所以需要格外区分一下各种拷贝方法。以进行更加可靠的编程,避免不必要的错误。

一 对象赋值

对象赋值实际是简单的对象引用。即当创建了一个对象,把该对象赋给另一个变量时,Python并没有重新创建一个新的一模一样的对象,而只是让该变量指向该对象(也可以说,拷贝了这个对象的引用)。


如下例所示,其中id()返回对象的内存地址。下例表明新变量y和原变量x指向的是同一块内存地址,即两者指向同一个对象。这表明,赋值时仅拷贝了对象的引用(即存储对象地址的变量)。

>>> x = 3.14
>>> y = x
>>> print([id(item) for item in [x,y]])
[1904199604888, 1904199604888]

下面的例子从另一个角度解释了这个问题。即,对person1修改信息后,person2的信息也发生了同样的变化。这也说明,两者指向的是同一个对象。

>>> person = ['name','Tel']
>>> person1 = person
>>> person2 = person
>>> person1[0] = 'joe'
>>> person1[1] = '13081851234'
>>> person1
['joe', '13081851234']
>>> person2
['joe', '13081851234']

下面的输出表示,赋值得到的新对象与原对象,不止对象相同(内存地址相同),内容也相同(内存地址相同)。

换句话说,组合对象相同,子对象也相同。(相同不止是说值相同,而是指内存地址相同,两者就是同一个)

>>> id(person1)
1904231981896
>>> id(person2)
1904231981896
>>> [x for x in person1]
['joe', '13081851234']
>>> [id(x) for x in person1]
[1904241329072, 1904241294960]
>>> [id(x) for x in person2]
[1904241329072, 1904241294960]

对象赋值的特点:拷贝生成的对象与原对象,两对象是相同的(两个对象的内存地址相同),内容也是相同的。

二 浅拷贝

注意:浅拷贝和深拷贝仅仅是对组合对象(或说容器类型)而言,所谓的组合对象(容器类型)就是包含了其他对象的对象,比如列表、类实例。而对于非容器类型,比如数字、字符串以及其他“原子”类型,没有被拷贝一说,产生的都是原对象的引用。

浅拷贝,是指创建一个新的对象,其内容是原对象中元素的引用。(拷贝的组合对象是新的,内容不是。换句话说,拷贝组合对象,不拷贝子对象)

从下面的例子可以看出,对a进行浅拷贝得到b,a和b指向内存中不同的 list对象,但他们的元素却指向相同的int对象(子对象)。

>>> a = [1,2,3]
>>> b = list(a)
>>> id(a)
1985581168840
>>> id(b)
1985580873352
>>> for x,y in zip(a,b):
	print(id(x),id(y))

1509869232 1509869232
1509869264 1509869264
1509869296 1509869296
常见的浅拷贝操作有:切片操作、工厂函数、对象的copy()方法、copy模块中的copy()函数。
使用示例如下:

浅拷贝的特点:拷贝生成的对象与原对象,两对象是不同的(内存地址不同),内容相同。

三 深拷贝
深拷贝,是指创建一个新的对象,然后递归拷贝原对象所包含的子对象。

深拷贝只有一种方式:copy模块的deepcopy函数。

从下例可以看出浅拷贝和深拷贝的区别:

>>> import copy
>>> a = [[1,2],[3,4],[5,6]]
>>> b = copy.copy(a) #浅拷贝得到b
>>> c = copy.deepcopy(a) #深拷贝得到c
>>> print(id(a),id(b)) #a和b不同
1985581099016 1985581097992
>>> for x,y in zip(a,b): #a和b的子对象相同
	print(id(x),id(y))

	
1985539661512 1985539661512
1985581168520 1985581168520
1985581168456 1985581168456
>>> print(id(a),id(c)) #a和c不同
1985581099016 1985581168392
>>> for x,y in zip(a,c):#a和c的子对象也不同
	print(id(x),id(y))

	
1985539661512 1985581097736
1985581168520 1985581098056
1985581168456 1985581098184
上例中容器对象的内容是可变对象(列表list),如果容器对象的内容是不可变对象呢?(可变对象:list、dict。不可变对象:int string float tuple)

>>> import copy
>>> a = [1,2,3]
>>> b = copy.deepcopy(a)
>>> print(id(a),id(b)) #组合对象不同(内存地址不同)
1845321737608 1845321799688
>>> for x,y in zip(a,b): #内容相同
	print(id(x),id(y)) 

	
1509869232 1509869232
1509869264 1509869264
1509869296 1509869296
>>> b[0] = 10
>>> b
[10, 2, 3]
>>> for x,y in zip(a,b): #改变了b[0],然而a[0]并没有随之变化
	print(id(x),id(y))

	
1509869232 1509869520
1509869264 1509869264
1509869296 1509869296
为什么上例中组合对象a和b的内容会相同呢?为什么改变b[0]后,a[0]没有随着变化呢?

皆因为组合对象的内容是不可变对象int。

因为内容是不可变对象,当需要一个新对象时,Python会返回已经存在的某个类型、值都与原对象相同的对象(在这里就是a的子对象了)的引用。这种机制并不会影响a和b的相互独立性,因为当两个对象指向同一个不可变对象时,对其中一个赋值不会影响到另外一个。

当对b[0]赋值时,b[0]是不可变对象,所以首先另开辟一块内存空间,创建一个新的对象并进行初始化,然后令b[0]指向该新对象(改变b[0]中存储的(引用)内存地址)。

总结:

1、赋值:简单地拷贝对象的引用,两个对象的id相同。
2、浅拷贝:创建一个新的组合对象,这个新对象与原对象共享内存中的子对象。
3、深拷贝:创建一个新的组合对象,同时递归地拷贝所有子对象,新的组合对象与原对象没有任何关联。虽然实际上会共享不可变的子对象,但不影响它们的相互独立性。


参考网址与书籍:

《Python核心编程(第二版)》

Python 赋值、浅拷贝、深拷贝的区别 —— CSDN

Python的赋值、深拷贝、浅拷贝的区别 — 米立 — 博客园

python 深拷贝和浅拷贝之可变和不可变对象总结— 沧海一粟 — 博客园

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张之海

若有帮助,客官打赏一分吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值