Python源碼分析(二) - List對象


  python中的高級特性之一就是內置了list,dict等。今天就先圍繞列表(List)進行源碼分析。

Python中的List對象(PyListObject)
  Python中的的PyListObject是對列表的一個抽象,內置了插入、添加、刪除等操作。不同List中存儲的元素的個數會是不同的,所以PyListObject是一個變長對象。而PyListObject中支持插入刪除等操作,可以在運行時動態地調整其所維護的內存和元素,所以它又是一個可變對象。

PyListObject的定義

在列表對象接口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對象的創建與維護

創建
  在列表對象的實現文件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建立聯系的。

  在創建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*元素列表占用的內存,重新擁抱新的對象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM