Python列表創(chuàng)建與銷毀及緩存池機制
列表的創(chuàng)建
創(chuàng)建列表,Python底層只提供了唯一一個Python/C API,也就是PyList_New。這個函數(shù)接收一個size參數(shù),允許我們在創(chuàng)建一個PyListObject對象時指定底層的PyObject *數(shù)組的長度。
PyObject *
PyList_New(Py_ssize_t size)
{
//聲明一個PyListObject *對象
PyListObject *op;
#ifdef SHOW_ALLOC_COUNT
static int initialized = 0;
if (!initialized) {
Py_AtExit(show_alloc);
initialized = 1;
}
#endif
//如果size小于0,直接拋異常
if (size < 0) {
PyErr_BadInternalCall();
return NULL;
}
//緩存池是否可用,如果可用
if (numfree) {
//將緩存池內(nèi)對象個數(shù)減1
numfree--;
//從緩存池中獲取
op = free_list[numfree];
//設(shè)置引用計數(shù)
_Py_NewReference((PyObject *)op);
#ifdef SHOW_ALLOC_COUNT
count_reuse++;
#endif
} else {
//不可用的時候,申請內(nèi)存
op = PyObject_GC_New(PyListObject, &PyList_Type);
if (op == NULL)
return NULL;
#ifdef SHOW_ALLOC_COUNT
count_alloc++;
#endif
}
//如果size等于0,ob_item設(shè)置為NULL
if (size <= 0)
op->ob_item = NULL;
else {
//否則的話,創(chuàng)建一個指定容量的指針數(shù)組,然后讓ob_item指向它
//所以是先創(chuàng)建PyListObject對象, 然后創(chuàng)建指針數(shù)組
//最后通過ob_item建立聯(lián)系
op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
if (op->ob_item == NULL) {
Py_DECREF(op);
return PyErr_NoMemory();
}
}
//設(shè)置ob_size和allocated,然后返回op
Py_SIZE(op) = size;
op->allocated = size;
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}我們注意到源碼里面有一個緩存池,是的,Python大部分對象都有自己的緩存池,只不過實現(xiàn)的方式不同。
列表的銷毀
創(chuàng)建PyListObject對象時,會先檢測緩存池free_list里面是否有可用的對象,有的話直接拿來用,否則通過malloc在系統(tǒng)堆上申請。列表的緩存池是使用數(shù)組實現(xiàn)的,里面最多維護80個PyListObject對象。
#ifndef PyList_MAXFREELIST #define PyList_MAXFREELIST 80 #endif static PyListObject *free_list[PyList_MAXFREELIST];
根據(jù)之前的經(jīng)驗我們知道,既然創(chuàng)建的時候能從緩存池中獲取,那么在執(zhí)行析構(gòu)函數(shù)的時候也要把列表放到緩存池里面。
static void
list_dealloc(PyListObject *op)
{
Py_ssize_t i;
PyObject_GC_UnTrack(op);
Py_TRASHCAN_SAFE_BEGIN(op)
//先釋放底層數(shù)組
if (op->ob_item != NULL) {
i = Py_SIZE(op);
//但是釋放之前,還有一件重要的事情
//要將底層數(shù)組中每個指針指向的對象的引用計數(shù)都減去1
//因為它們不再持有對"對象"的引用
while (--i >= 0) {
Py_XDECREF(op->ob_item[i]);
}
//然后釋放底層數(shù)組所占的內(nèi)存
PyMem_FREE(op->ob_item);
}
//判斷緩沖池里面PyListObject對象的個數(shù),如果沒滿,就添加到緩存池
//注意:我們看到執(zhí)行到這一步的時候, 底層數(shù)組已經(jīng)被釋放掉了
if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
//添加到緩存池的時候,是添加到尾部
//獲取的時候也是從尾部獲取
free_list[numfree++] = op;
else
//否則的話就釋放掉PyListObject對象所占的內(nèi)存
Py_TYPE(op)->tp_free((PyObject *)op);
Py_TRASHCAN_SAFE_END(op)
}我們知道在創(chuàng)建一個新的PyListObject對象時,實際上是分為兩步的,先創(chuàng)建PyListObject對象,然后創(chuàng)建底層數(shù)組,最后讓PyListObject對象中的ob_item成員指向這個底層數(shù)組。
同理,在銷毀一個PyListObject對象時,先銷毀ob_item維護的底層數(shù)組,然后再釋放PyListObject對象自身(如果緩存池已滿)。
現(xiàn)在可以很清晰地明白了,原本空蕩蕩的緩存池其實是被已經(jīng)死去的PyListObject對象填充了。在以后創(chuàng)建新的PyListObject對象時,Python會首先喚醒這些死去的PyListObject對象,給它們一個洗心革面、重新做人的機會。但需要注意的是,這里緩存的僅僅是PyListObject對象,對于底層數(shù)組,其ob_item已經(jīng)不再指向了。
從list_dealloc中我們看到,PyListObject對象在放進緩存池之前,ob_item指向的數(shù)組就已經(jīng)被釋放掉了,同時數(shù)組中指針指向的對象的引用計數(shù)會減1。所以最終數(shù)組中這些指針指向的對象也大難臨頭各自飛了,或生存、或毀滅,總之此時和PyListObject之間已經(jīng)沒有任何聯(lián)系了。
但是為什么要這么做呢?為什么不連底層數(shù)組也一起維護呢?可以想一下,如果繼續(xù)維護的話,數(shù)組中指針指向的對象永遠不會被釋放,那么很可能會產(chǎn)生懸空指針的問題,所以這些指針指向的對象所占的空間必須交還給系統(tǒng)(前提是沒有其它指針指向了)。
但是實際上,是可以將PyListObject對象維護的底層數(shù)組進行保留的,即:只將數(shù)組中指針指向的對象的引用計數(shù)減1,然后將數(shù)組中的指針都設(shè)置為NULL,不再指向之前的對象了,但是并不釋放底層數(shù)組本身所占用的內(nèi)存空間。
因此這樣一來,釋放的內(nèi)存不會交給系統(tǒng)堆,那么再次分配的時候,速度會快很多。但是這樣帶來一個問題,就是這些內(nèi)存沒人用也會一直占著,并且只能供PyListObject對象的ob_item指向的底層數(shù)組使用。因此Python還是為避免消耗過多內(nèi)存,采取將底層數(shù)組所占的內(nèi)存交還給了系統(tǒng)堆這樣的做法,在時間和空間上選擇了空間。
lst1 = [1, 2, 3] print(id(lst1)) # 1243303086208 # 扔到緩存池中,放在數(shù)組的尾部 del lst1 # 從緩存池中獲取,也會從數(shù)組的尾部開始拿 lst2 = [1, 2, 3] print(id(lst2)) # 1243303086208 # 因此打印的地址是一樣的
小結(jié)
作為一個功能強大的數(shù)據(jù)結(jié)構(gòu),多花些時間是有必要的。
到此這篇關(guān)于Python列表創(chuàng)建與銷毀及緩存池機制的文章就介紹到這了,更多相關(guān)Python 列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python dataframe列應(yīng)用正則表達式篩選方式
這篇文章主要介紹了python dataframe列應(yīng)用正則表達式篩選方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
淺析pip安裝第三方庫及pycharm中導(dǎo)入第三方庫的問題
這篇文章主要介紹了淺析pip安裝第三方庫及pycharm中導(dǎo)入第三方庫的問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
詳解pytest實現(xiàn)mark標(biāo)記功能詳細介紹
這篇文章主要介紹了詳解pytest實現(xiàn)mark標(biāo)記功能詳細介紹,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
python 解決flask uwsgi 獲取不到全局變量的問題
今天小編就為大家分享一篇python 解決flask uwsgi 獲取不到全局變量的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12
快速解釋如何使用pandas的inplace參數(shù)的使用
這篇文章主要介紹了快速解釋如何使用pandas的inplace參數(shù)的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07

