python中的高級特性之一就是內置了list,dict等。今天就先圍繞列表(List)進行源碼分析。python
Python中的List對象(PyListObject)
Python中的的PyListObject是對列表的一個抽象,內置了插入、添加、刪除等操做。不一樣List中存儲的元素的個數會是不一樣的,因此PyListObject是一個變長對象。而PyListObject中支持插入刪除等操做,能夠在運行時動態地調整其所維護的內存和元素,因此它又是一個可變對象。緩存
PyListObject的定義app
在列表對象接口listobject.h中,PyListObject的定義是:函數
typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated;
其中,ob_item是指向了元素列表所在的內存塊的首地址,allocated維護了當前列表中的可容納的元素的總數。源碼分析
咱們知道,用戶選用list正是爲了能夠頻繁的執行插入或刪除等操做,若是是須要存多少就申請多大的內存,這種內存管理顯然是低效的。那麼Python內部是怎麼實現的呢?這就與剛纔所提到的allocated有關了,咱們知道,在PyObject_VAE_HEAD中有一個ob_size,在PyListObject中,每一次須要申請內存時,總會申請一大塊內存存,這時申請的總內存的大小記錄記錄在allocated中,而其中實際被使用了的內存的數量則記錄在ob_size中。學習
PyListObject對象的建立與維護spa
建立
在列表對象的實現文件listObject.c文件中,咱們能夠看到,Python對於建立一個列表,提供了惟一的一條途徑,就是PyList_New(),對應的代碼以下:指針
PyObject * PyList_New(Py_ssize_t size) { PyListObject *op; size_t nbytes; #ifdef SHOW_ALLOC_COUNT static int initialized = 0; if (!initialized) { Py_AtExit(show_alloc); initialized = 1; } #endif if (size < 0) { PyErr_BadInternalCall(); return NULL; } //進行溢出檢查 if ((size_t)size > PY_SIZE_MAX / sizeof(PyObject *)) return PyErr_NoMemory(); nbytes = size * sizeof(PyObject *); //爲PyListObject對象申請空間,使用到緩衝池技術 if (numfree) { numfree--; op = free_list[numfree]; _Py_NewReference((PyObject *)op); #ifdef SHOW_ALLOC_COUNT count_reuse++; #endif } else { op = PyObject_GC_New(PyListObject, &PyList_Type); if (op == NULL) return NULL; #ifdef SHOW_ALLOC_COUNT count_alloc++; #endif } //爲PyListObject對象中維護的元素列表申請空間 if (size <= 0) op->ob_item = NULL; else { op->ob_item = (PyObject **) PyMem_MALLOC(nbytes); if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); } memset(op->ob_item, 0, nbytes); } Py_SIZE(op) = size; op->allocated = size; _PyObject_GC_TRACK(op); return (PyObject *) op; }
首先,進行溢出檢查。接下來,就是List對象的建立了,Python中的list對象其實是分爲兩部分的,一是PyListObject對象自己,二是PyListObject對象維護的元素列表,而這兩塊內存是經過ob_item創建聯繫的。code
在建立PyListObject對象時,首先檢查緩衝池中free_list是否有可用的對象,若是有,則直接使用,若沒有可用對象,則經過PyObject_GC_New在系統堆中申請內存,在Python2.7.12中,free_lists中最多維護80個PyListObject對象。對象
當建立了新的PyListObject對象以後,會根據調用PyList_New是傳遞的size參數建立ListObject對象所維護的元素列表。
設置元素
元素建立好了,下一步就是向元素中添加元素了,經過PyList_SetItem()實現:
int PyList_SetItem(register PyObject *op, register Py_ssize_t i, register PyObject *newitem) { register PyObject *olditem; register PyObject **p; if (!PyList_Check(op)) { Py_XDECREF(newitem); PyErr_BadInternalCall(); return -1; } //索引檢查 if (i < 0 || i >= Py_SIZE(op)) { Py_XDECREF(newitem); PyErr_SetString(PyExc_IndexError, "list assignment index out of range"); return -1; } //存放元素 p = ((PyListObject *)op) -> ob_item + i; olditem = *p; *p = newitem; Py_XDECREF(olditem); return 0; }
首先,進行類型檢查,而後進行索引的有效性檢查,當類型檢查和索引檢查均經過的時候,就能夠將待加入的Pyobject*指針放在指定的位置了。
插入元素
插入元素和設置元素的不一樣在於:設置元素不會將ob_item指向的內存發生變化,而插入內存可能會致使ob_item指向的內存發生變化。
好比:
a = [0, 0, 0, 0]; a[2] = 3; print a [0, 0, 3, 0] a.insert(2,3) [0,0,2,3,0]
這個插入動做確實致使了元素列表的內存發生變化。關於插入,在列表中有兩種操做:insert()和append()。
insert經過調用PyList_Insert()方法來完成元素的插入動做,首先判斷PyListObject對象有足夠的內存容納咱們指望插入的元素,而後調用list_resize()函數調整列表容量,肯定插入點,插入元素。
在調整PyListObject對象所維護對象的內存時,Python使用了兩種方法:
1. 當newsize < allocated && newsize > allocated/2 時,簡單調整ob_size;
2. 調用realloc,從新分配空間。
append是經過調用PyList_Append()方法,在第ob_size+1個位置上插入。
刪除元素
對於一個容器而言,除了建立,插入這些操做,確定是還得有刪除操做的。
remove() a = [1, 2, 3, 4] print a.remove(3) [1, 2, 4]
remove()調用了listremove操做。Python會對整個列表進行遍歷,將待刪除的元素與PyListObject中的每一個元素一一比較,比較操做經過PyObject_RichCompareBool完成,當返回值大於0,則表示列表中有和待刪元素匹配的元素,則Python發現以後調用list_ass_slice刪除該元素。
PyListObject對象緩衝池
在這以前,咱們學習到,在建立PyListObject對象時,會首先檢查緩衝區中的free_lists中是否有可用的對象。
在建立一個新的的對象時,實際也是分爲兩部,首先建立PyListObject對象,而後建立PyListObject對象所維護的元素列表,與之對應,在銷燬一個list時,銷燬的過程也是分離的,首先銷燬PyListObject所維護的元素列表,而後釋放PyListObject對象自身。。
在刪除PylsitObject對象自身時,Python會先檢查咱們開始提到的那個緩衝池free_list,查看其中緩存的PyListObject的數量是否已經滿了,若是沒有,就將待刪除的PyListObject對象放到緩衝池中,以備後用。
所以,那個在Python啓動時空蕩蕩的緩衝池原來都是被本應該死去的PyListObject對象給填充了,在之後須要建立新的PyListObject的時候,Python會首先喚醒這些對象,從新分配Pyobject*元素列表佔用的內存,從新擁抱新的對象。