Python 之 = [:] copy deepcopy

写在前面

已经见多很多这样的Blog,为什么还想自己写呢?原因很简单,每当学到一个知识时候,尝试着对自己或者别人讲解这个知识点,当别人能听懂的时候,就算是真的理解了。

重要部分

首先,要分清楚id()函数、is和= =运算符 的使用以及区别。id()是返回一个对象的地址;is 比较两个变量的引用是否指向同一个对象;= = 比较两个变量的值是否相等

其次,Python中一切皆对象,所有的对象拥有三个属性:id、type、value,Python对象分两种类型,一是不可变对象,二是可变对象。
不可变对象:该对象所指向的内存中的值不能被改变。一个变量指向的是不可变对象的引用,当对这个变量的值做修改时,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,这时变量的指向已经发生改变,由原来的指向新开辟的地址。
可变对象:该对象所指向的内存中的值可以被改变。变量改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,变量指向的地址并没有发生改变。

a = 100  #id(a) ->140736904075200
a += 10  #id(a) ->140736904078400
L = ['C#', 'C++', 'Java']   #id(L) ->1627814636224
L.append('Python')			#id(L) ->1627814636224

常见的不可变对象:数字、字符串、元组
常见的可变对象:列表、字典、集合

接下来,看一下这个例子

import copy
is_same_addr = lambda v1,v2:(id(v1)==id(v2))

e = 10	#e指向不可变对象
v_1 = e				#直接赋值
v_2 = copy.copy(e)	#浅拷贝
print is_same_addr(v_1, e)	#>> True
print is_same_addr(v_2, e)  #>> True

显然,当变量e指向的是不可变对象,采用直接赋值或者拷贝的方式让另一个变量v等于e,实际上两个变量指向的是同一个变量的引用,并没有复制出新的副本

接着,再往下看

e = ['e','jianl', ['Python', 'C++']]
v_1 = e					#1 赋值
v_2 = e[:]				#2 切片
v_3 = copy.copy(e)		#3 浅拷贝
v_4 = copy.deepcopy(e)	#4 深拷贝

print is_same_addr(v_1, e)	#>> True
print is_same_addr(v_2, e)	#>> False
print is_same_addr(v_3, e)	#>> False
print is_same_addr(v_4, e)	#>> False

可以看出,除了赋值的方式是复制变量e的引用外, 其余方式都是有开辟新的内存,不算是 ‘假复制’,
因此,我想如果v_2 、v_3、 v_4 不小心发生改变,应该不会影响原变量e的值,依据是他们所指向并不是同一个对象, 真的是这样吗?敲代码看一下

v_2[0] = 'v_2'
v_3[0] = 'v_3'
v_4[0] = 'v_4'
# 输出各个变量的值
>> e  : ['e',   'jianl', ['Python', 'C++']]
>> v_1: ['e',   'jianl', ['Python', 'C++']]
>
>> v_2: ['v_2', 'jianl', ['Python', 'C++']]
>> v_3: ['v_3', 'jianl', ['Python', 'C++']]
>> v_4: ['v_4', 'jianl', ['Python', 'C++']]

似乎是这样,不过到这里还没完,接着试一下对列表里的可变对象修改操作,看下结果又会怎样呢?

v_2[2][0] = 'C#'
v_3[2][0] = 'C#'

分别对v_2[2] 、v_3[2]所指向的列表作同样的修改,两种情况输出的结果一致,而且变量e的值被修改了

e  : ['e', 'jianl', ['C#', 'C++']]
v_1: ['e', 'jianl', ['C#', 'C++']]
v_2: ['e', 'jianl', ['C#', 'C++']]
v_3: ['e', 'jianl', ['C#', 'C++']]
v_4: ['e', 'jianl', ['Python', 'C++']]

同样,再对v_4[2]所指向的列表作同样的修改

v_4[2][0] = 'C#'

结果,只有v_4的值被修改了

e  : ['e', 'jianl', ['Python', 'C++']]
v_1: ['e', 'jianl', ['Python', 'C++']]
v_2: ['e', 'jianl', ['Python', 'C++']]
v_3: ['e', 'jianl', ['Python', 'C++']]
v_4: ['e', 'jianl', ['C#', 'C++']]

为什么会这样呢?来做一个验证, 对比变量指向的对象成员中为可变变量的地址

print is_same_addr(v_1, e),is_same_addr(v_1[2], e[2])	#True 	True
print is_same_addr(v_2, e),is_same_addr(v_2[2], e[2])	#False 	True
print is_same_addr(v_3, e),is_same_addr(v_3[2], e[2])	#False 	True
print is_same_addr(v_3, e),is_same_addr(v_4[2], e[2])	#False 	False

水落石出,利用切片[:]属性或者copy()函数实现对一个变量赋值,属于浅拷贝,只是拷贝对象的一部分不可变类型的数据,对于对象里的可变类型对象属性,仅仅是拷贝一个引用;而使用deepcopy()函数实现的是深拷贝,将变量所指向的对象整个复制,开辟新的空间,再由另一个变量指向这块新的空间。

最后

几句话总结:
1 对象有类型,变量无类型,变量是对象的标签;
2 对指向不可变对象变量的值作修改,实际上是改变变量的指向,对象本身是不可能被修改的;指向可变对象变量的值发生改变,实际上是对象本身被修改了;
3 v = e,实际上是将e的引用赋值给v, v e指向同一个变量;
4 v= e[:] 及 v=copy(e),浅拷贝,只复制部分数据,而e所指向的对象中可变类型属性,只是拷贝引用;
5 v = deepcopy(e), 深拷贝,e指向的整个对象被复制,再由v指向;

参考文章:
https://www.jianshu.com/p/9ed9b5ce7bb0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值