Python 列表对象实现原理 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lzjun
V2EX    Python

Python 列表对象实现原理

  •  
  •   lzjun
    lzjun567 2015 年 12 月 18 日 3045 次点击
    这是一个创建于 3765 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Python 列表对象实现原理

    Python 中的列表基于 PyListObject 实现,列表支持元素的插入、删除、更新操作,因此 PyListObject 是一个变长对象(列表的长度随着元素的增加和删除而变长和变短),同时它还是一个可变对象(列表中的元素根据列表的操作而发生变化,内存大小动态的变化), PyListObject 的定义:

    typedef struct { # 列表对象引用计数 int ob_refcnt; # 列表类型对象 struct _typeobject *ob_type; # 列表元素的长度 int ob_size; /* Number of items in variable part */ # 真正存放列表元素容器的指针, list[0] 就是 ob_item[0] PyObject **ob_item; # 当前列表可容纳的元素大小 Py_ssize_t allocated; } PyListObject; 

    咋一看 PyListObject 对象的定义非常简单,除了通用对象都有的引用计数( ob_refcnt )、类型信息( ob_type ),以及变长对象的长度( ob_size )之外,剩下的只有 ob_item ,和 allocated , ob_item 是真正存放列表元素容器的指针,专门有一块内存用来存储列表元素,这块内存的大小就是 allocated 所能容纳的空间。 alloocated 是列表所能容纳的元素大小,而且满足条件:

    • 0 <= ob_size <= allocated
    • len(list) == ob_size
    • ob_item == NULL 时 ob_size == allocated == 0

    pylistobject

    列表对象的创建

    PylistObject 对象的是通过函数 PyList_New 创建而成,接收参数size,该参数用于指定列表对象所能容纳的最大元素个数。

    // 列表缓冲池, PyList_MAXFREELIST 为 80 static PyListObject *free_list[PyList_MAXFREELIST]; //缓冲池当前大小 static int numfree = 0; PyObject *PyList_New(Py_ssize_t size) { PyListObject *op; //列表对象 size_t nbytes; //创建列表对象需要分配的内存大小 if (size < 0) { PyErr_BadInternalCall(); return NULL; } /* Check for overflow without an actual overflow, * which can cause compiler to optimise out */ if ((size_t)size > PY_SIZE_MAX / sizeof(PyObject *)) return PyErr_NoMemory(); nbytes = size * sizeof(PyObject *); if (numfree) { numfree--; op = free_list[numfree]; _Py_NewReference((PyObject *)op); } else { op = PyObject_GC_New(PyListObject, &PyList_Type); if (op == NULL) return NULL; } 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); } # 设置 ob_size Py_SIZE(op) = size; op->allocated = size; _PyObject_GC_TRACK(op); return (PyObject *) op; } 

    创建过程大致是:

    1. 检查 size 参数是否有效,如果小于 0 ,直接返回 NULL ,创建失败
    2. 检查 size 参数是否超出 Python 所能接受的大小,如果大于 PY_SIZE_MAX ( 64 位机器为 8 字节,在 32 位机器为 4 字节),内存溢出。
    3. 检查缓冲池 free_list 是否有可用的对象,有则直接从缓冲池中使用,没有则创建新的 PyListObject ,分配内存。
    4. 初始化 ob_item 中的元素的值为 Null
    5. 设置 PyListObject 的 allocated 和 ob_size 。

    PyListObject 对象的缓冲池

    free_list 是 PyListObject 对象的缓冲池,其大小为 80 ,那么 PyListObject 对象是什么时候加入到缓冲池 free_list 的呢?答案在 list_dealloc 方法中:

    static void list_dealloc(PyListObject *op) { Py_ssize_t i; PyObject_GC_UnTrack(op); Py_TRASHCAN_SAFE_BEGIN(op) if ( i = Py_SIZE(op); while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } PyMem_FREE(op->ob_item); } if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) free_list[numfree++] = op; else Py_TYPE(op)->tp_free((PyObject *)op); Py_TRASHCAN_SAFE_END(op) } 

    当 PyListObject 对象被销毁的时候,首先将列表中所有元素的引用计数减一,然后释放 ob_item 占用的内存,只要缓冲池空间还没满,那么就把该 PyListObject 加入到缓冲池中(此时 PyListObject 占用的内存并不会正真正回收给系统,下次创建 PyListObject 优先从缓冲池中获取 PyListObject ),否则释放 PyListObject 对象的内存空间。

    列表元素插入

    设置列表某个位置的值时,如“ list[1]=0 ”,列表的内存结构并不会发生变化,而往列表中插入元素时会改变列表的内存结构:

    static int
    ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
    {
    // n 是列表元素长度
    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) == -1) 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; } 

    相比设置某个列表位置的值来说,插入操作要多一次 PyListObject 容量大小的调整,逻辑是 list_resize ,其次是挪动 where 之后的元素位置。

    // newsize : 列表新的长度 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); /* check for integer overflow */ 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 *))) 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; } 

    满足 allocated >= newsize && newsize >= (allocated /2)时,简单改变 list 的元素长度, PyListObject 对象不会重新分配内存空间,否则重新分配内存空间,如果newsize<allocated/2,那么会减缩内存空间,如果newsize>allocated,就会扩大内存空间。当newsize==0时内存空间将缩减为 0 。

    !python_list_resize

    总结

    • PyListObject 缓冲池的创建发生在列表销毁的时候。
    • PyListObject 对象的创建分两步:先创建 PyListObject 对象,然后初始化元素列表为 NULL 。
    • PyListObject 对象的销毁分两步:先销毁 PyListObject 对象中的元素列表,然后销毁 PyListObject 本身。
    • PyListObject 对象内存的占用空间会根据列表长度的变化而调整。

    参考:

    原文:python 列表实现原理

    1 条回复    2018-03-06 13:28:40 +08:00
    joeHuang
        1
    joeHuang  
       2018 年 3 月 6 日
    参考 listobject.c: https://github.com/lzjun567/python2.7/blob/master/Objects/listobject.c
    其中的第 48 行 new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);好像是错误的。正确的应该是 new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6)+newsize;
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5421 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 39ms UTC 09:13 PVG 17:13 LAX 02:13 JFK 05:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86