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

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

不可变对象

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

#定义三个变量
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的地址不同了。')

执行结果:spa

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 的值并无改变,由于它们的地址不同了。code

 可变对象

就是变量所指向的内存中的值是能够改变的。若是要修改变量值,不用开辟新的内存空间,直接在原地改变值,变量的地址不会改变。对象

下面的例子建立了两个 list,值是相同的,打印地址能够看出它们的地址是不同的。再建立一个s3,将s1的值赋给s3,而后改变s3的值,发现s1的值也跟着变了,由于它们的指向的地址空间是同样。blog

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)、字符串、元组都是不可变类型。class

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

浅拷贝与深拷贝

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

以一个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 --------------------------

相关文章
相关标签/搜索