Python中的可变对象与不可变对象、浅拷贝与深拷贝

Laozizuiku 2019-10-20

Python中的对象分为可变与不可变,有必要了解一下,这会影响到python对象的赋值与拷贝。而拷贝也有深浅之别。

不可变对象

简单说就是某个对象存放在内存中,这块内存中的值是不能改变的,变量指向这块内存,如果要改变变量的值,只能再开辟一块内存,放入新值,再让变量指向新开辟的内存。

#定义三个变量
f=22
n=22
z=f   
print(‘f=%s,n=%s,z=%s‘ %(f,n,z))
print(‘f的地址:‘,id(f))#id用于获取变量内存地址
print(‘n的地址:‘,id(n))
print(‘z的地址:‘,id(z))
print(‘注意:f、n、z的地址是一样的。\n‘)
n=9  #改变n的值
z=6  #改变z的值
print(‘f=%s,n=%s,z=%s‘ %(f,n,z))
print(‘f的地址:‘,id(f))
print(‘n的地址:‘,id(n))
print(‘z的地址:‘,id(z))
print(‘注意:f、n、z的地址不一样了。‘)

执行结果:

f=22,n=22,z=22
f的地址:8790949926368
n的地址:8790949926368
z的地址:8790949926368
注意:f、n、z的地址是一样的。

f=22,n=9,z=6
f的地址:8790949926368
n的地址:8790949925952
z的地址:8790949925856
注意:f、n、z的地址不一样了。

上面的例子可以看出,当变量的值改变,它的地址也跟着改变了,就证明当变量的值改变时,python并没有将原地址空间的内容改变,而是又重新分配了一块内存空间,放上新值,然后将变量指向新开辟的空间地址。对于赋值,可以看出将 f 的值赋给 z,它们的地址是一样的,但是当改变 z 的值,z的地址也跟着改变了,f 的值并没有改变,因为它们的地址不一样了。

 可变对象

就是变量所指向的内存中的值是可以改变的。如果要修改变量值,不用开辟新的内存空间,直接在原地改变值,变量的地址不会改变。

下面的例子创建了两个 list,值是相同的,打印地址可以看出它们的地址是不一样的。再创建一个s3,将s1的值赋给s3,然后改变s3的值,发现s1的值也跟着变了,因为它们的指向的地址空间是一样。

s1=[3,6,9]
s2=[3,6,9]
print(s1,s2)
print(‘s1的地址:%s,s2的地址:%s‘ %(id(s1),id(s2)))
#可以看出s1和s2的地址是不一样的,虽然值一样。
s3=s1#将s1的值赋给s3
print(‘s3的地址:%s‘ %(id(s3)))#s1和s3的地址是一样的
s3.append(11)#在s3里面添加一个元素
print(s1,s3)#s1的值也跟着改变了

执行结果:

执行结果:
[3, 6, 9] [3, 6, 9]
s1的地址:92282952,s2的地址:92283976
s3的地址:92282952
[3, 6, 9, 11] [3, 6, 9, 11]

在python中,

  • 数值类型(int 和 float)、字符串、元组都是不可变类型。

  • 列表、字典、集合是可变类型。

浅拷贝与深拷贝

拷贝表面看就是复制一个一样的对象,但它们最本质的区别就是复制出来的这个对象的地址是否和原对象的地址一样,就是在内存中存放这两个对象的位置是否发生了改变。由浅入深可分为三个层次(直接赋值、浅拷贝、深拷贝)。

以一个list为例子,演示三种不同层次的拷贝:首先构造一个list,这个list里面有两种不同类型的元素,分别为不可变元素1,2,3和可变元素[‘FF‘,‘WW‘],即ff=[1,2,3,[‘FF‘,‘WW‘]]。通过分析list对象即其里面包含元素的地址是否改变可以清晰看出拷贝的深浅之别。

第一层:直接赋值:构造一个 nn,直接将 ff 赋值给 nn,这种情况下 ff 和 nn 以及它们的元素所指向的地址是一样的,改变任何一个对象,另一个也一样改变,因为这个两个对象及其元素指向的内存空间是一样的,它们没有自己的独立内存空间。

#浅拷贝和深拷贝:直接赋值
ff=[1,2,3,[‘FF‘,‘WW‘]]
nn=ff  #将ff直接赋值给nn
print(‘ff的地址:%s,ff[3]的地址:%s‘ %(id(ff),id(ff[3])))
print(‘nn的地址:%s,nn[3]的地址:%s‘ %(id(nn),id(nn[3])))
nn.append(4)#修改nn,在后面添加一个元素
nn[0]=99#修改nn元素值
nn[3].append(‘NN‘)#修改nn,在第三个list元素里添加一个元素
print(nn)#nn变了
print(ff)#ff也跟着变了

执行结果:

ff的地址:95127176,ff[3]的地址:95277576
nn的地址:95127176,nn[3]的地址:95277576
[99, 2, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4]
[99, 2, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4]

第二层:浅拷贝:使用语句nn=copy.copy(ff)完成拷贝,通过观察变量地址的变化来理解拷贝的不同。打印地址发现对象 nn 和原对象 ff 地址已经不一样了,但元素的地址是一样的,然后对 nn 做一下改变。

  1. 为nn添加一个元素,ff与nn地址不同,ff没有受到影响。

  2. 将nn[1]的值改变,nn[1]为数值,是不可变对象,改变就是新开辟了内存。nn[1]的地址发生了变化,ff[1]的地址没有改变,所以ff[1]的值没有发生变化。

  3. 将nn[3]的值改变,nn[3]为列表,是可变对象,nn[3]的值变地址没有变,因为ff[3]和nn[3]的地址相同,所以ff[3]的值也就改变了。

可见,浅拷贝复制的是元素的地址引用,如果元素是不可变类型,修改就更新了地址,和原对象的地址不同了,所以原对象不会受到影响,当元素是可变类型,修改没有改变地址,这样原对象也就跟着变化。

跟着变化。
#浅拷贝和深拷贝:浅拷贝
import copy
ff=[1,2,3,[‘FF‘,‘WW‘]]
nn=copy.copy(ff)#浅拷贝
print(‘ff的地址:‘,id(ff))
print(‘ff[1]的地址:‘,id(ff[1]))#ff里的不可变元素
print(‘ff[3]的地址:‘,id(ff[3]),‘\n‘)#ff里的可变元素

print(‘nn的地址:‘,id(nn))
print(‘nn[1]的地址:‘,id(nn[1]))#nn里的不可变元素
print(‘nn[3]的地址:‘,id(nn[3]),‘\n‘)#nn里的可变元素

nn.append(4)#修改nn,在后面添加一个元素
nn[1]=55 #修改不可变元素的值
nn[3].append(‘NN‘) #修改可变元素的值
print(‘ff:‘,ff)
print(‘nn:‘,nn,‘\n‘)
print(‘ff的地址:%s,ff[1]的地址:%s,ff[3]的地址:%s‘ %(id(ff),id(ff[1]),id(ff[3])))
print(‘nn的地址:%s,nn[1]的地址:%s,nn[3]的地址:%s‘ %(id(nn),id(nn[1]),id(nn[3])))

执行结果:

ff的地址:96794888
ff[1]的地址:8790949925728
ff[3]的地址:96796168

nn的地址:96796040
nn[1]的地址:8790949925728
nn[3]的地址:96796168

ff: [1, 2, 3, [‘FF‘, ‘WW‘, ‘NN‘]]
nn: [1, 55, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4] 

ff的地址:96794888,ff[1]的地址:8790949925728,ff[3]的地址:96796168
nn的地址:96796040,nn[1]的地址:8790949927424,nn[3]的地址:96796168

第三层:深拷贝改变任何一个对象都对另一个没有影响,它们是独立的。通过观察地址可以看出,对于不可变元素,重新创建一份似乎有点多余,反正修改就会刷新地址,所以ff[1]和nn[1]的地址还是一样的,主要看可变对象ff[3]和nn[3],深拷贝后它们的地址已经不一样了。

#浅拷贝和深拷贝:深拷贝
import copy
ff=[1,2,3,[‘FF‘,‘WW‘]]
nn=copy.deepcopy(ff)#深拷贝
print(‘ff的地址:‘,id(ff))
print(‘ff[1]的地址:‘,id(ff[1]))#ff里的不可变元素
print(‘ff[3]的地址:‘,id(ff[3]),‘\n‘)#ff里的可变元素

print(‘nn的地址:‘,id(nn))
print(‘nn[1]的地址:‘,id(nn[1]))#nn里的不可变元素
print(‘nn[3]的地址:‘,id(nn[3]),‘\n‘)#nn里的可变元素

nn.append(4)  #修改nn,在后面添加一个元素
nn[1]=55  #修改不可变元素的值
nn[3].append(‘NN‘) #修改可变元素的值
print(‘ff:‘,ff)
print(‘nn:‘,nn,‘\n‘)
print(‘ff的地址:%s,ff[1]的地址:%s,ff[3]的地址:%s‘ %(id(ff),id(ff[1]),id(ff[3])))
print(‘nn的地址:%s,nn[1]的地址:%s,nn[3]的地址:%s‘ %(id(nn),id(nn[1]),id(nn[3])))

执行结果:

ff的地址:93244616
ff[1]的地址:8791030272864
ff[3]的地址:93241416

nn的地址:93244552
nn[1]的地址:8791030272864
nn[3]的地址:93244744

ff: [1, 2, 3, [‘FF‘, ‘WW‘]]
nn: [1, 55, 3, [‘FF‘, ‘WW‘, ‘NN‘], 4] 

ff的地址:93244616,ff[1]的地址:8791030272864,ff[3]的地址:93241416
nn的地址:93244552,nn[1]的地址:8791030274560,nn[3]的地址:93244744

通俗一点:

  • 直接赋值:两个对象你中有我,我中有你,同住一间房。

  • 浅拷贝:两个对象分居中,没离婚,藕断丝连,有部分个人空间。

  • 深拷贝:两个对象彻底离婚了,都不在一个城市了,各住各家,互不影响。

-------------------------- END --------------------------

相关推荐