1. 程式人生 > >python資料記憶體形式-引用與物件的認識

python資料記憶體形式-引用與物件的認識

這篇文章主要是對python中的資料進行認識,對於很多初學者來講,其實資料的認識是最重要的,也是最容易出錯的。本文結合資料與記憶體形態講解python中的資料,內容包括:

  • 引用與物件
  • 可變資料型別與不可變資料型別
  • 引用傳遞與值傳遞
  • 深拷貝與淺拷貝

id函式:你可以通過python的內建函式 id() 來檢視物件的身份(identity),這個所謂的身份其實就是 物件 的記憶體地址

一、引用與物件:引用與物件的關係:
這裡寫圖片描述

#建立兩個物件
name1='wupeiqi'
name2='alex'

這裡寫圖片描述

物件:當建立資料物件時,在記憶體中會儲存物件的值,這個值就是物件自己;(字串物件:”wupeiqi”)
引用:

物件儲存在記憶體空間,外部想要使用物件的值,需要使用引用,就是‘name1’,’name2’。記憶體會儲存物件的引用數量,當某個物件的引用數量為0時,物件會被回收。

二、可變資料型別與不可變資料型別
1,資料分類:

  • 可變資料型別:列表list和字典dict
  • 不可變資料型別:整型int、浮點型float、字串型string和元組tuple

這裡的可變不可變,是指記憶體中的那塊內容(value)是否可以被改變。如果是不可變型別,在對物件本身操作的時候,必須在記憶體中新申請一塊區域(因為老區域不可變)。如果是可變型別,對物件操作的時候,不需要再在其他地方申請記憶體,只需要在此物件後面連續申請

(+/-)即可,也就是它的address會保持不變,但區域會變長或者變短。

(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