1. 程式人生 > >python,可變物件,不可變物件,深拷貝,淺拷貝。

python,可變物件,不可變物件,深拷貝,淺拷貝。

學習整理,若有問題,歡迎指正。

python 可變物件,不可變物件

  1. 可變物件

該物件所指定的記憶體地址上面的值可以被改變,變數被改變後,其所指向的記憶體地址上面的值,直接被改變,沒有發生複製行為,也沒有發生開闢新的記憶體地址行為。

python可變物件有,列表,字典,set集合

列如:

a = ['1','2','3']

print(id(a))
2275736586376

a.append('1')

print(a)
['1', '2', '3', '1']

print(id(a))
2275736586376

我們可以看到,在向 a 列表內追加資料的時候,列表 a 前後的id沒有發生變化,所以記憶體地址內,沒有開闢新的空間,而是直接在原指定記憶體地址上面修改的。

圖解:

        2. 不可變物件

該物件所指定記憶體中的值不可以被改變,在改變某個物件的值的時候,由於其記憶體中的值不可以被改變,所以,會把原來的值複製一份再進行改變,這樣就會計算機會開闢

一段新的記憶體空間來儲存新的值,python 不可變物件有 int str float number,tuple,None

例如:

c = 'Hello'

print(id(c))
2237718097848

print(c)
Hello

c = c + 'World'

print(id(c))
2237720838960

print
(c) HelloWorld

我們可以看到,c變數在追加的時候,兩個id是不一樣的,所以記憶體地址會發生變化,此變化為,將c變數所指向的記憶體,複製一份,然後再做更改。

圖解:

此時,c的記憶體是指向 HelloWorld的,若Hello記憶體,長時間沒有變數指定,則垃圾回收機制會自動回收的。

         3. 等於賦值,並不會產生獨立的物件單獨存在,而是給原來的資料塊打上一個新的標籤,其中一個標籤值被改變的時候,另一個標籤也隨之改變。

alist = [1,2,3,['a','b','c']]
b = alist
#此處id相同,所以指向同以記憶體地址 print(id(alist)) 2030874445768 print(id(b)) 2030874445768 #向alist內追加元素 alist.append(5) print(alist) [1, 2, 3, ['a', 'b', 'c'], 5] print(b) [1, 2, 3, ['a', 'b', 'c'], 5] #向alist內的列表追加元素 alist[3].append("d") print(alist) [1, 2, 3, ['a', 'b', 'c', 'd'], 5] print(b) [1, 2, 3, ['a', 'b', 'c', 'd'], 5] print(id(alist)) 2030874445768 print(id(b)) 2030874445768

由上面程式碼看出,alist所指向 [1,2,3,['a','b','c']] 記憶體地址,b 也是指定的[1,2,3,['a','b','c']]記憶體地址(id相同),所以a.append(5)後,alist變化,b也變化

圖解:

由上圖可以看出,無論alist做什麼操作,b的記憶體地址都是指向alist的,所以alist的任何變化,b都會跟著變化。

       4. 淺複製,淺複製分為兩種情況

  • 第一種情況,淺複製的值是不可變物件(數值,字串,元組)時,與等值複製的是一樣的,物件的id值與淺複製物件的id是一樣的。
import copy

a = 'abcde'
b = a
print(id(a))
2030878234584
print(id(b))
2030878234584

c = copy.copy(a)
print(id(c))
2030878234584

#當改變a的值
a = a + 'fg'
print(id(a))
2030878515304
#由於字串為不可變物件,所以id(a)會發生變化,但是b,c不會發生變化,還是指向原有記憶體
print(id(b))
2030878234584
print(id(c))
2030878234584

由上面程式碼可以看出 b = a 其實b,a是指向同一塊記憶體的(id)相同,c=copy.copy(a)也是和a,b指向同一塊記憶體的(id相同),當a發生變化的時候,a會複製原有值,開闢一塊新的記憶體

出來,此時,c與b還是指向原有記憶體(觀察id)

圖解:

  • 當淺複製的值是可變物件(列表和元組)時會產生一個“不是那麼獨立的物件”存在。有兩種情況:
    • 複製的 物件中無 複雜 子物件,原來值的改變並不會影響淺複製的值,同時淺複製的值改變也並不會影響原來的值。原來值的id值與淺複製原來的值不同。

         當淺複製的值是可變物件(列表,字典)時,改變的值不是 複雜子物件 程式碼如下:

 

list1 = ['1','2','3']
print(id(list1))
2030878577608

list2 = list1
print(id(list2))
2030878577608

list3 = list1.copy()
print(id(list2))
2030878577608
#上面三者id都一致

#然後在list1後面append一個新的元素,檢視變化情況
list1.append('4')
print(list1)
['1', '2', '3', '4', '4']
print(id(list1))
2030878577608
print(list2)
['1', '2', '3', '4', '4']
print(id(list2))
2030878577608
print(list3)
['1', '2', '3']
print(id(list3))
2030878531400
#list3的值與list1,list2的值,不一樣,且id也不一樣了

當list2 = list1 的時候,完全指向同一塊記憶體空間,list3 = list1.copy()的時候,list3也指向同一塊記憶體空間,當list1發生變化的時候,list2與list1開闢出一塊新的空間用來改變,list3還指向原有空間(或者相反)

圖解:

  • 當淺複製的值是可變物件(列表,字典)時,改變的值是 複雜子物件 程式碼如下:
l1 = [1,2,['a','b']]
l2 = l1
l3 = copy.copy(l2)
l4 = copy.deepcopy(l3)
print(id(l1))
2030878621960
print(id(l2))
2030878621960
print(id(l3))
2030878622664
print(id(l4))
2030878622728
#id可以看出,l1,l2的id沒有改變,但是,l3,l4的id發生了變化
l1[2].append('a')
print(id(l1))
2030878621960
print(l1)
[1, 2, ['a', 'b', 'a']]
print(l3)
[1, 2, ['a', 'b', 'a']]
print(l4)
[1, 2, ['a', 'b']]
#l1[2].append('a')是改變l1內的可變列表的值,l1的id沒有變化,值有變化,l3的值有變化,但是l4的值沒有變化。
l1.append(3)
print(id(l1))
2030878621960
print(l1)
[1, 2, ['a', 'b', 'a'], 3]
print(l2)
[1, 2, ['a', 'b', 'a'], 3]
print(l3)
[1, 2, ['a', 'b', 'a']]
print(l4)
[1, 2, ['a', 'b']]
#l1.append(3)是改變l1的值,l1id沒有變化,l3的值沒有被改變(還是和上一步的值一樣),l4與l1初始值一樣的。

 當改變 複雜子物件中的元素時,淺拷貝值發生了變化; 當改變的值不是複雜子物件,淺拷貝的值沒有發生變化。因為 淺拷貝 ,複雜子物件的儲存方式是 作為 引用 方式儲存的,所以修改 淺拷貝的值 和原來的值都可以 改變 複雜子物件的值。

圖解:

 

  • 深度拷貝
    • 即將被複制物件完全再複製一遍作為獨立的新個體單獨存在。所以改變原有被複制物件不會對已經複製出來的新物件產生影響
    • 上圖中已經有說明了。