Python中的可變對象與不可變對象、淺拷貝與深拷貝

  • 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 ————————–