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 --------------------------

相關文章
相關標籤/搜索