python資料記憶體形式-引用與物件的認識
這篇文章主要是對python中的資料進行認識,對於很多初學者來講,其實資料的認識是最重要的,也是最容易出錯的。本文結合資料與記憶體形態講解python中的資料,內容包括:
- 引用與物件
- 可變資料型別與不可變資料型別
- 引用傳遞與值傳遞
- 深拷貝與淺拷貝
(id函式:你可以通過python的內建函式 id() 來檢視物件的身份(identity),這個所謂的身份其實就是 物件 的記憶體地址)
一、引用與物件:引用與物件的關係:
#建立兩個物件
name1='wupeiqi'
name2='alex'
物件:當建立資料物件時,在記憶體中會儲存物件的值,這個值就是物件自己;(字串物件:”wupeiqi”)
引用:
二、可變資料型別與不可變資料型別
1,資料分類:
- 可變資料型別:列表list和字典dict
- 不可變資料型別:整型int、浮點型float、字串型string和元組tuple
這裡的可變不可變,是指記憶體中的那塊內容(value)是否可以被改變。如果是不可變型別,在對物件本身操作的時候,必須在記憶體中新申請一塊區域(因為老區域不可變)。如果是可變型別,對物件操作的時候,不需要再在其他地方申請記憶體,只需要在此物件後面連續申請
(1)python中的不可變資料型別,不允許變數的值發生變化,如果改變了變數的值,相當於是新建了一個物件,而對於相同的值的物件,在記憶體中則只有一個物件,內部會有一個引用計數來記錄有多少個變數引用這個物件;
(2)可變資料型別,允許變數的值發生變化,即如果對變數進行append、+=等這種操作後,只是改變了變數的值,而不會新建一個物件,變數引用的物件的地址也不會變化,不過對於相同的值的不同物件,在記憶體中則會存在不同的物件,即每個物件都有自己的地址,相當於記憶體中對於同值的物件儲存了多份,這裡不存在引用計數,是實實在在的物件
2,不可變資料型別:不可變是指物件本身的值是不可變的(當你建立a=1整型物件,用a去引用它,記憶體中的物件1是不變得,當執行a=2時,只是重新建立了物件2,用a引用,如果1物件沒有其他引用會被回收)
>>> x = 1
>>> id(x)
31106520
>>> y = 1
>>> id(y)
31106520
>>> x = 2
>>> id(x)
31106508
>>> y = 2
>>> id(y)
31106508
>>> z = y
>>> id(z)
31106508
解釋:這裡的不可變大家可以理解為x引用的地址處的值是不能被改變的,也就是31106520地址處的值在沒被垃圾回收之前一直都是1,不能改變,如果要把x賦值為2,那麼只能將x引用的地址從31106520變為31106508,相當於x = 2這個賦值又建立了一個物件,即2這個物件,然後x、y、z都引用了這個物件,所以int這個資料型別是不可變的,如果想對int型別的變數再次賦值,在記憶體中相當於又建立了一個新的物件,而不再是之前的物件。從下圖中就可以看到上面程式的過程。
3,可變物件:可變是指物件本身的值是可變的(list,dict物件的值其實是引用了其他物件,當改變物件的值時,其實是引用了不同的物件)
>>> a = [1, 2, 3]
>>> id(a)
41568816
>>> a = [1, 2, 3]
>>> id(a)
41575088
>>> a.append(4)
>>> id(a)
41575088
>>> a += [2]
>>> id(a)
41575088
>>> a
[1, 2, 3, 4, 2]
解釋:(1)進行兩次a = [1, 2, 3]操作,兩次a引用的地址值是不同的,也就是說其實建立了兩個不同的物件,這一點明顯不同於不可變資料型別,所以對於可變資料型別來說,具有同樣值的物件是不同的物件,即在記憶體中儲存了多個同樣值的物件,地址值不同。
(2)我們對列表進行新增操作,分別a.append(4)和a += [2],發現這兩個操作使得a引用的物件值變成了上面的最終結果,但是a引用的地址依舊是41575088,也就是說對a進行的操作不會改變a引用的地址值,只是在地址後面又擴充了新的地址,改變了地址裡面存放的值,所以可變資料型別的意思就是說對一個變數進行操作時,其值是可變的,值的變化並不會引起新建物件,即地址是不會變的,只是地址中的內容變化了或者地址得到了擴充。下圖對這一過程進行了圖示,可以很清晰地看到這一過程。
三、引用傳遞與值傳遞:可變物件為引用傳遞,不可變物件為值傳遞。(函式傳值)
1,引用傳遞:當傳遞列表或者字典時,如果改變引用的值,就修改了原始的物件。
# 添加了一個string型別的元素新增到末尾
def ChangeList(lis):
lis.append('hello i am the addone')
print lis
return
lis = [1, 2, 3]
ChangeList(lis)
print lis
輸出:
[1,2,3, 'hello i am the addone']
[1,2, 3,'hello i am the addone']
2,值傳遞:當傳遞不可變物件時,如果改變引用的值,只是建立了不同的物件,原始物件並沒有改變。
def ChangeString(string):
string = 'i changed as this'
print string
return
string = 'hello world'
ChangeString(string)
print string
輸出:
i changed as this
hello world
四、深拷貝與淺拷貝:
copy.copy() 淺拷貝;copy.deepcopy() 深拷貝。淺拷貝是新建立了一個跟原物件一樣的型別,但是其內容是對原物件元素的引用。這個拷貝的物件本身是新的,但內容不是。拷貝序列型別物件(列表\元組)時,預設是淺拷貝。
1,賦值拷貝:
賦值,只是建立一個變數,該變數指向原來記憶體地址:n4 = n3 = n2 = n1 = “123/’Wu’”
2,淺拷貝:在記憶體中只額外建立第一層資料
import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 456]}
n3 = copy.copy(n1)
import copy
a = [1,[[2,3],5],3]
b = a.copy() #copy.copy(a)
print(id(a[1]))
print(id(b[1]))
c = copy.deepcopy(a)
print(id(c[1]))
輸出:
3021497843400
3021497843400
3021497854728
3,深拷貝:在記憶體中將所有的資料重新建立一份(排除最後一層,即:python內部對字串和數字的優化)
import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 456]}
n4 = copy.deepcopy(n1)
參考文獻:
http://blog.csdn.net/dan15188387481/article/details/49864613
https://www.cnblogs.com/lfpython/p/7207747.html
https://www.cnblogs.com/huamingao/p/5809936.html
https://www.cnblogs.com/jiangzhaowei/p/5740913.html