[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指定了初始化列表的元素个数。  

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;
        }
View Code

    [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指向的内存发生变化。 

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;
            }
View Code

    Python内部通过调用PyList_Insert来完成元素的插入动作,而PyList_Insert实际上是调用了Python内部的ins1.在ins1中,首先要检查是否有足够内存空间来容纳插入的元素。因此,在[1]处调用了list_resize函数来保证条件一定能成立。這个函数一定是改变了PyObject*列表的大小。

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;
            }
View Code   

    在调整PyListObject对象所维护的列表的内存时,Python分两种情况处理:
      1.newsize<allocated && newsize> allocated /2:简单调整ob_size值
      2.其他情况.调整realloc,重新分配空间

    将PyListObject的空间调整后,函数ins1在实际插入元素之前还需要在代码[2]处首先确定插入点,然后在[3]处开始搬运元素,将插入点之后的所有元素向下挪动一个位置,這样,在插入点就能空出一个位置来。

    

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;
        }
View Code

    进行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对象是从哪里获得的,是在何时创建的?其实,这一切都发生在对象被销毁的时候。

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)
        }
View Code    

  在创建一个新的list时,过程分两步:首先创建PyListObject对象,然后创建PyListObject对象所维护的元素列表。与之对应的,在销毁一个list时,也是分离的。首先销毁PyListObject对象所维护的元素列表,然后再代码[2]处释放PyListObject对象自身。
  代码[1]改变list中每个元素的引用计数,然后释放内存。在删除PyListObject对象自身时,python会检查我们开始提到的那个缓冲池free_lists,查看其中缓存的PyListObject的数量是否已经满了。如果没有,就将该待删除的PyListObject对象放到缓冲池中,以备后用。
  所以,缓冲池都是一个死去的Pylistobject对象填充了。在以后创建新的PylistoBJECT时候,python首先唤醒这些已经'死去'的Pylistobject对象。缓存的仅仅是Pylistobject对象,而没有PyObject*元素列表。

 

  

posted @ 2017-06-08 19:24  看雪。  阅读(731)  评论(0编辑  收藏  举报