1. 程式人生 > >Python 列表原始碼解析

Python 列表原始碼解析

列表作為python 最常用的一種資料型別, 一直很好奇其是如何實現動態的擴充套件的,於是上github看了原始碼(https://github.com/python/cpython/blob/master/Include/listobject.hhttps://github.com/python/cpython/blob/master/Objects/listobject.c),結合《python原始碼剖析》分享一下自己的收穫!

列表原始碼定義

#ifndef Py_LIMITED_API
typedef struct {
    PyObject_VAR_HEAD
    /* Vector of pointers to
list elements. list[0] is ob_item[0], etc. */ PyObject **ob_item; /* ob_item contains space for 'allocated' elements. The number * currently in use is ob_size. * Invariants: * 0 <= ob_size <= allocated * len(list) == ob_size * ob_item == NULL implies ob_size == allocated == 0
* list.sort() temporarily sets allocated to -1 to detect mutations. * * Items must normally not be NULL, except during construction when * the list is not yet visible outside the function that builds it. */ Py_ssize_t allocated; } PyListObject; #endif

allocated 記錄了這個列表申請的記憶體大小, 假如一個列表中可以容納10個元素,則allocated 就是10,當我們往列表中新增或者刪除元素時這個大小都會跟著改變

ob_size 則是記錄了當前列表中 已經使用的大小

allocated 和 ob_size 都和記憶體管理有關,python的列表總是會被頻繁的新增或者刪除元素,因此頻繁的申請釋放記憶體顯然是不明智的,所以python的列表在建立時總是會申請一大塊記憶體,申請的記憶體大小就記錄在 allocated 上, 已經使用的就記錄在 ob_size

列表建立

通過PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size); 方法來建立
該方法需要指定 建立列表容量的大小
建立列表程式碼如下

PyList_New(Py_ssize_t size)
{
    PyListObject *op;
#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 (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
    }
    if (size <= 0)
        op->ob_item = NULL;
    else {
        op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
        if (op->ob_item == NULL) {
            Py_DECREF(op);
            return PyErr_NoMemory();
        }
    }
    Py_SIZE(op) = size;
    op->allocated = size;
    _PyObject_GC_TRACK(op);
    return (PyObject *) op;
}

新建列表主要做了:

  • 1 引數檢查 列表大小不能是 0

  • 2 檢查是否有快取可用

  • 3 記憶體檢查 檢查新建的列表大小不能超過 記憶體大小

  • 4 申請記憶體 新建列表

  • 5 為allocated 設定初始化的值

列表設定元素的實現

lst = list()
lst[0] = "hello world"

當通過 PyObject_GC_New 建立列表之後,其實裡面的元素都是null

lst

假如 我們要給lst 第n元素賦值 其實就是通過 PyList_SetItem(PyObject *op, Py_ssize_t i,
PyObject *newitem)
方法來實現的

程式碼如下:

PyList_SetItem(PyObject *op, Py_ssize_t i,
               PyObject *newitem)
{
    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;
    Py_XSETREF(*p, newitem);
    return 0;
}

設定元素的過程大致的步驟是

  • 1 引數型別檢查

  • 2 索引 有效性檢查 不可超出索引

  • 3 設定元素

列表 insert 插入元素的實現

插入元素 使用的是ins1(PyListObject *self, Py_ssize_t where, PyObject *v) 方法來實現
原始碼 如下

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

    if (list_resize(self, n+1) < 0)
        return -1;

    if (where < 0) {
        where += n;
        if (where < 0)
            where = 0;
    }
    if (where > n)
        where = n;
    items = self->ob_item;
    for (i = n; --i >= where; )
        items[i+1] = items[i];
    Py_INCREF(v);
    items[where] = v;
    return 0;
}

其主要實現過程 如下

  • 1 引數檢查

  • 2 從新調整列表容量 通過 list_resize 方法確定 是否需要申請記憶體

  • 3 確定插入點

  • 4 插入元素

可以看到列表插入時 都會將後面的位置的元素重新移動

lst_insert

列表的append 實現

通過 PyList_Append(PyObject *op, PyObject *newitem) 方法來實現

原始碼如下:

PyList_Append(PyObject *op, PyObject *newitem)
{
    if (PyList_Check(op) && (newitem != NULL))
        return app1((PyListObject *)op, newitem);
    PyErr_BadInternalCall();
    return -1;
}

其主要是呼叫了 app1(PyListObject *self, PyObject *v) 來實現的

app1 方法主要做了以下工作

  • 1 引數檢查

  • 2 容量檢查

  • 3 呼叫 list_resize 方法檢查是否需要申請記憶體

  • 4 新增元素

列表轉元組

列表轉元組其實就是新建一個大小和列表一樣大小的陣列,並將該列表內的元素新增到元組中

原始碼如下:

PyObject *
PyList_AsTuple(PyObject *v)
{
    PyObject *w;
    PyObject **p, **q;
    Py_ssize_t n;
    if (v == NULL || !PyList_Check(v)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    n = Py_SIZE(v);
    w = PyTuple_New(n);
    if (w == NULL)
        return NULL;
    p = ((PyTupleObject *)w)->ob_item;
    q = ((PyListObject *)v)->ob_item;
    while (--n >= 0) {
        Py_INCREF(*q);
        *p = *q;
        p++;
        q++;
    }
    return w;
}

列表的反轉 reverse

列表的反轉是就地反轉的,不會生成新的列表, 迴圈當前列表 使用了一個第三變數,然後將元素移位

static void
reverse_slice(PyObject **lo, PyObject **hi)
{
    assert(lo && hi);

    --hi;
    while (lo < hi) {
        PyObject *t = *lo;
        *lo = *hi;
        *hi = t;
        ++lo;
        --hi;
    }
}

關於 列表的 extend 方法 看了原始碼才知道,只要是可迭代物件都可以放入extend(), 字典,元組,字串都可以

部分 原始碼定義 如下

if (PyList_CheckExact(iterable) || PyTuple_CheckExact(iterable) ||
                (PyObject *)self == iterable) {
        PyObject **src, **dest;
        iterable = PySequence_Fast(iterable, "argument must be iterable");
        if (!iterable)
            return NULL;

還有很多的方法等著你們去探索和發現, 更多列表實現的原始碼請看:
https://github.com/python/cpython/blob/master/Objects/listobject.c