[python源码剖析]List对象
本文主要从以下几个方面来叙述:
1.PyListObject对象
2.PyListObject对象的创建与维护(包括对象的创建、删除、插入以及设置)
3.PyListObject对象缓冲池 一、PyListObject对象
一、PyListObject对象
首先来看看PyListObject对象的定义:
typedef struct { PyObject_VAR_HEAD //这里面有个ob_size PyObject **ob_item; // 指向元素列表首地址的指针,list[0]==ob_item[0] Py_ssize_t allocated;//指出列表中可容纳的元素的总数 } PyListObject;
PyObject_VAR_HEAD:这个宏定义我们在第一次讲解的时候就已经阐述了。其中有个ob_size变量,这个变量存放着当前PyListObject对象存放的元素的个数.
ob_item:这个二级指针指向了一段内存空间的首地址.
allocated:这个变量存放的是列表中元素的总个数.
对于一个PyListObject对象,一定会有以下关系:
0<= ob_size <= allocated
len(list) == ob_size
ob_item == NULL 意味着ob_size == allocated == 0
二、PyListObject对象的创建与维护(包括对象的创建、删除、插入以及设置)
1.对象的创建
唯一途径----PyList_New,参数size指定了初始化列表的元素个数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
PyObject *PyList_New(Py_ssize_t size){ PyListObject *op; size_t nbytes; if (size < 0) { PyErr_BadInternalCall(); return NULL; } if ((size_t)size > PY_SIZE_MAX / sizeof(PyObject *)) return PyErr_NoMemory(); //[1]内存数量计算,溢出检查 nbytes = size * sizeof(PyObject *); //[2]为PyListObject对象申请空间 if (numfree) { //缓冲池可以用 numfree--; op = free_list[numfree]; _Py_NewReference((PyObject *)op); } else { //缓冲池不用 op = PyObject_GC_New(PyListObject, &PyList_Type); if (op == NULL) return NULL; } //[3]为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; return (PyObject *) op; }
[1]处计算创建对象需要的内存总数,根据传进来的size参数,来申请内存.
[2]在创建新的PyListObject对象时,会检查缓冲池free_list中是否有可用对象,如果有,则会直接使用這个对象;如果缓冲池中所有对象都不可用,则会通过PyObject_GC_New在系统堆中申请内存,创建新的PylistObject对象。在默认情况下,free_list中最多维护80个PyListObject对象。
#define PyList_MAXFREELIST 80 //维护对象的个数 static PyListObject *free_list[PyList_MAXFREELIST]; static int numfree = 0; //缓冲区
在[3]出,会立即根据传进来的size参数创建PyListObject对象所维护的元素列表。在创建的PyListObject*列表,其中每一个元素都被初始化为NULL。
最后,完成PyListObject对象及其维护的列表的创建之后,Python会调整该PylistObject对象,用于维护元素列表中元素数量的ob_size和allocated两个变量。
2.对象的设置
在第一个PyListObject创建的时候,这时的num_free_list = 0 ,所以代码会绕过对象缓冲池,转而调用Py_Object_GC_new在系统堆上创建一个新的PyListObject对象,假设我们创建的PyListOBject是包含6个元素的PyListObject,也是通过PyList_New(6)来创建PyListObject对象。
设置元素代码:
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; } //[1]索引检查 if (i < 0 || i >= Py_SIZE(op)) { Py_XDECREF(newitem); PyErr_SetString(PyExc_IndexError, "list assignment index out of range"); return -1; } //[2]设置元素 p = ((PyListObject *)op) -> ob_item + i; olditem = *p; *p = newitem; Py_XDECREF(olditem); return 0; }
当我们在python中运行list[3] = 100,在python内部,就是调用PyList_SetItem来完成这个动作的。首先python会进行类型检查,在[1]处进行索引检查。当类型检查和索引检查都顺利通过之后,python在代码[2]处将待加入的PyObject*指针放到指定的位置,然后调整引用计数,将这个位置原来存放的对象的引用计数减1.olditem很可能为NULL,比如向一个新创建的PyListObject对象加入元素,就会碰到这样的情况,所以必须使用Py_XDECREF.
3.向列表中插入元素
设置元素不会导致ob_item指向的内存发生改变。而插入元素的动作则有可能使得ob_item指向的内存发生变化。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem){ //类型检查 if (!PyList_Check(op)) { PyErr_BadInternalCall(); return -1; } return ins1((PyListObject *)op, where, newitem); } static int ins1(PyListObject *self, Py_ssize_t where, PyObject *v){ Py_ssize_t i, n = Py_SIZE(self); PyObject **items; if (v == NULL) { PyErr_BadInternalCall(); return -1; } if (n == PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_OverflowError, "cannot add more objects to list"); return -1; } //[1]调整列表容量 if (list_resize(self, n+1) == -1) return -1; //[2]确定插入点 if (where < 0) { //处理索引为赋值情况 where += n; if (where < 0) where = 0; } if (where > n) where = n; //[3]插入元素 items = self->ob_item; for (i = n; --i >= where; ) items[i+1] = items[i]; Py_INCREF(v); items[where] = v; return 0; }
Python内部通过调用PyList_Insert来完成元素的插入动作,而PyList_Insert实际上是调用了Python内部的ins1.在ins1中,首先要检查是否有足够内存空间来容纳插入的元素。因此,在[1]处调用了list_resize函数来保证条件一定能成立。這个函数一定是改变了PyObject*列表的大小。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static int list_resize(PyListObject *self, Py_ssize_t newsize){ PyObject **items; size_t new_allocated; Py_ssize_t allocated = self->allocated; //不需要 重新申请内存 if (allocated >= newsize && newsize >= (allocated >> 1)) { assert(self->ob_item != NULL || newsize == 0); Py_SIZE(self) = newsize; return 0; } //计算重新申请的内存大小 new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6); //检查整数是否溢出 if (new_allocated > PY_SIZE_MAX - newsize) { PyErr_NoMemory(); return -1; } else { new_allocated += newsize; } if (newsize == 0) new_allocated = 0; //扩展列表 items = self->ob_item; if (new_allocated <= (PY_SIZE_MAX / sizeof(PyObject *))) //调用C中的realloc PyMem_RESIZE(items, PyObject *, new_allocated); else items = NULL; if (items == NULL) { PyErr_NoMemory(); return -1; } self->ob_item = items; Py_SIZE(self) = newsize; self->allocated = new_allocated; return 0; }
在调整PyListObject对象所维护的列表的内存时,Python分两种情况处理:
1.newsize<allocated && newsize> allocated /2:简单调整ob_size值
2.其他情况.调整realloc,重新分配空间
将PyListObject的空间调整后,函数ins1在实际插入元素之前还需要在代码[2]处首先确定插入点,然后在[3]处开始搬运元素,将插入点之后的所有元素向下挪动一个位置,這样,在插入点就能空出一个位置来。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
list中的append操作 //python提供的C API int PyList_Append(PyObject *op, PyObject *newitem){ if (PyList_Check(op) && (newitem != NULL)) return app1((PyListObject *)op, newitem); PyErr_BadInternalCall(); return -1; } //与append对应的C函数 static PyObject *listappend(PyListObject *self, PyObject *v){ if (app1(self, v) == 0) Py_RETURN_NONE; return NULL; } static int app1(PyListObject *self, PyObject *v){ Py_ssize_t n = PyList_GET_SIZE(self); assert (v != NULL); if (n == PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_OverflowError, "cannot add more objects to list"); return -1; } if (list_resize(self, n+1) == -1) return -1; Py_INCREF(v); PyList_SET_ITEM(self, n, v); return 0; }
进行append动作时候,添加的元素是添加在第ob_size + 1个位置上(即list[ob_size]),而不是第allocated个位置上.添加到扩展内存上。
在app1中调用list_resize时,由于newsize(8)在5和10之间,所以不需要分配内存空间。直接将101放在第8个位置。
4.删除列表中的元素
list中删除元素的方法是remove(),下面我们来看看其源码:
当python执行list.remove(3)时,pyListObject中的listremove操作会被激活。
static PyObject *listremove(PyListObject *self, PyObject *v){ Py_ssize_t i; for (i = 0; i < Py_SIZE(self); i++) { ///比较list中的元素与待删除的元素v int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ); if (cmp > 0) { if (list_ass_slice(self, i, i+1, (PyObject *)NULL) == 0) Py_RETURN_NONE; return NULL; } else if (cmp < 0) return NULL; } PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list"); return NULL; }
python会对整个列表进行遍历,在遍历Pylistobject中所有元素的过程中,将待删除的元素与Pylistobject中的每一个元素进行一一比较,比较操作是通过Pyobject_RichCompareBool完成的。如果返回值大于0,则表示列表中的某个元素与待删除的元素匹配。一旦在列表中发现匹配的元素,python将立即调用list_ass_slice删除该元素。
它的功能类似于:
a[ilow:ihigh] = v if v !=NULL
del a[ilow:ihigh] if v ==NULL
三、PyListObject对象缓冲池
在上面的源码中我们会看到一个变量----free_lists.这个就是存放List缓冲池指针的变量。
我们很想知道free_lists中缓存的PyListObject对象是从哪里获得的,是在何时创建的?其实,这一切都发生在对象被销毁的时候。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static void list_dealloc(PyListObject *op){ int i; PyObject_GC_UnTrack(op); Py_TRASHCAN_SAFE_BEGIN(op) //[1]销魂PyListObject对象维护的元素列表 if (op->ob_item != NULL) { i = Py_SIZE(op); while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } PyMem_FREE(op->ob_item); } //[2]释放PyListObject自身 if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) free_list[numfree++] = op; else Py_TYPE(op)->tp_free((PyObject *)op); Py_TRASHCAN_SAFE_END(op) }
在创建一个新的list时,过程分两步:首先创建PyListObject对象,然后创建PyListObject对象所维护的元素列表。与之对应的,在销毁一个list时,也是分离的。首先销毁PyListObject对象所维护的元素列表,然后再代码[2]处释放PyListObject对象自身。
代码[1]改变list中每个元素的引用计数,然后释放内存。在删除PyListObject对象自身时,python会检查我们开始提到的那个缓冲池free_lists,查看其中缓存的PyListObject的数量是否已经满了。如果没有,就将该待删除的PyListObject对象放到缓冲池中,以备后用。
所以,缓冲池都是一个死去的Pylistobject对象填充了。在以后创建新的PylistoBJECT时候,python首先唤醒这些已经'死去'的Pylistobject对象。缓存的仅仅是Pylistobject对象,而没有PyObject*元素列表。