oid 值 記憶體使用 Python記憶體引用機制

2021-10-12 18:51:52 字數 3137 閱讀 5235

python是當前使用最方便快捷的程式語言。作為乙個使用python的程式設計師,理解內部機制是寫好**的基礎。其中,首當其衝的就是最基礎的變數,以及其背後的記憶體引用機制。

python中的變數記憶體機制類似c++中的引用,即變數是乙份記憶體的引用,每個變數不一定都佔據乙份單獨的記憶體空間,可能有多個變數對應同乙個記憶體空間的情況。這裡我們可以通過用變數賦值和用值賦值兩種情況來進一步理解

>>> a = 257; b = a  

>>> id(a) == id(b) #傳的是a的引用,位址與a相同

true

>>> a = 257; b = 257

>>> id(a) == id(b) #單獨分配了一塊記憶體,值為257,位址與a不同

false

這裡大家會有個疑問,為什麼舉例使用的是整型的257。如果使用整型其他值或字串浮點等型別會發生什麼呢?不妨來試一下

>>> a = 1; b = 1

>>> id(a) == id(b)

true

>>> a = 1.1; b = 1.1

>>> id(a) == id(b)

false

>>> a = "1"; b = "1"

>>> id(a) == id(b)

true

根據上面的嘗試,可以看出整型1和字串型別都沒有單獨分配記憶體,相同值的變數實際上是同一塊記憶體的引用。而浮點型別則是單獨分配了記憶體。

這裡涉及到python快取小資料的機制,即為了效能方面的考慮,對整型[-5, 256]和字串型別進行快取,當初始化這部分值的變數時,不再單獨分配記憶體。

先看定義,這裡討論的都是python的基本資料型別,自定義類都屬於可變型別:

對不可變型別,比較好理解,數字和字串值變化了,或者是變數引用指向乙個新的位址,或者是根據變化後的值新分配一塊記憶體,變數的引用指向這塊記憶體。此外元組本身就定義為不可變,值發生變化只能是重新定義。對可變型別,可以看到這三種型別都是由元素組合而成,元素的變化或增減都不改變型別本身的引用。

>>> a = 1; b = a

>>> id(a) == id(b)

true

>>> b = 2

>>> id(a) == id(b) #不可變資料型別,值變位址變

false

>>>

>>> a = [1,2,3]; b = a

>>> id(a) == id(b)

true

>>> b[0] = 11

>>> a

[11, 2, 3]

>>> id(a) == id(b) #可變資料型別,值變位址不變

true

需要說明的是,在函式傳參時,傳遞的也是引用,和可變不可變資料型別無關。在有些技術部落格上會出現這樣的敘述:

python不允許程式設計師選擇採用傳值還是傳引用。python引數傳遞採用的肯定是「傳物件引用」的方式。實際上,這種方式相當於傳值和傳引用的一種綜合。如果函式收到的是乙個可變物件(比如字典或者列表)的引用,就能修改物件的原始值——相當於通過「傳引用」來傳遞物件。如果函式收到的是乙個不可變物件(比如數字、字元或者元組)的引用,就不能直接修改原始物件——相當於通過「傳值』來傳遞物件。
這段敘述中,後半段的結論沒問題,但是前半段的敘述不夠準確,很容易讓讀者產生誤解。python引數傳遞實際上是傳遞引用,這和可變和不可變資料型別沒有關係。「傳值」的含義在於分配一塊新的記憶體空間,而函式傳參並不一定符合這一點。因此對於可變和不可變型別,理解的重點在於值的變化,而不在於傳遞

>>> def func(a):

return a

>>> a = 1

>>> b = func(a) #傳參並不一定是傳值

>>> id(a) == id(b)

true

因此,對於可變資料型別,在賦值和傳參時,我們需要特別注意 值的修改會反映到所有的引用上。多個變數指向同乙個list時,任何乙個變數對list值進行改動,都會影響其他變數。這點我自己也是踩坑多次。對於這種情況,我們可以採用淺拷貝的方式為變數單獨分配記憶體。下面是幾個踩坑的例子。

#直接用乘n的方式生成的是多個引用,位址是相同的

>>> a = [[1,2,3]] * 3

>>> a

[[1, 2, 3], [1, 2, 3], [1, 2, 3]]

>>> a[0][0] = 11

>>> a

[[11, 2, 3], [11, 2, 3], [11, 2, 3]]

>>> [id(_) for _ in a]

[2325771410696, 2325771410696, 2325771410696]

#傳參傳的也是引用,class裡可能會有多次賦值,難以察覺

>>> class d:

def __init__(self, lst):

self.lst = lst

def getfirst(self):

return self.lst.pop(0)

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

>>> d = d(a)

>>> d.getfirst()

1>>> a #a, d.lst是同一物件的兩個引用

[2, 3]

#兩種淺拷貝方式

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

>>> b = a.copy()

>>> id(a) == id(b)

false

>>> c = a[:]

>>> id(a) == id(c)

false

python變數是乙份記憶體的引用

變數賦值時,如果等號右側是個引用,則直接傳遞引用;如果等號右側是值,則新分配一塊記憶體。整型[-5, 256]和字串型別是特例,有快取小資料機制,不單獨分配記憶體。

可變與不可變資料型別的區別在於值變化時引用是否變化,即是否重新分配記憶體。可變型別包括列表、字典和集合;不可變型別包括數字、字串、元組。

對於列表、字典和集合,在賦值和傳參時一定要多加注意,防止同引用變數的修改造成錯誤,保險的做法是賦值時採用淺拷貝的方式。

python修改記憶體值 python 記憶體修改

程序模組 import win32process import win32con import win32gui import win32api import ctypes process all access 0x000f0000 0x00100000 0xfff 找窗體 win win32gui...

python記憶體模型 Python結構的記憶體大小

這些答案都收集淺層尺寸資訊。我懷疑訪問此問題的訪問者最終將在這裡回答以下問題 此複雜物件在記憶體中有多大?這裡有乙個很好的答案 https 重點 import sys def get size obj,seen none recursively finds size of objects size ...

python記憶體管理錯誤的 Python記憶體管理

python採用block pool arenas三種結構來管理記憶體申請。入口 申請nbytes的記憶體 undef pyobject malloc void pyobject malloc size t nbytes block bp poolp pool poolp next uint siz...