Python源码解析之List


Posted in Python onMay 21, 2021

一、列表结构体

创建列表C语言底层的结构体

lists = []
list.append('name')
list.append('age')
list.append('grade')
typedef struct{
	struct _object *_ob_next;
	struct _object *_ob_prev; 	// python内部将对象放在链表进行内存管理
	Py_ssize_t ob_refcnt;		// 引用计数器,就是多少变量用了它
	PyObject **ob_item;			// 指针的指针,存列表的元素
	Py_ssize_t ob_size;			// 已有元素个数
	Py_ssize_t allocated;		// 列表容量,可容纳个数
} PyListObject;

c源码来自 listobject.c

二、创建列表

name_list = [ ]

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;
    }
    /* 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);
#ifdef SHOW_ALLOC_COUNT
        count_reuse++;
#endif
    } else {
        op = PyObject_GC_New(PyListObject, &PyList_Type);
        if (op == NULL)
            return NULL;Py
#ifdef SHOW_ALLOC_COUNT
        count_alloc++;
#endif
    }

    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中插入一个元素时,扩容连续的内存地址(容量),在内存创建需要插入的内容p,将地址*p放入list的空间中,所以,PyListObject的ob_item是指针的指针

Python源码解析之List

扩容的曲线一般就是0,4,8,16,24…

// 添加元素
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;
    // 将元素添加到ob_item,v
    Py_INCREF(v);
    PyList_SET_ITEM(self, n, v);
    return 0;
}
  • 扩容
// 扩容机制
 // newsize: 已存在元素个数+1
static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
    PyObject **items;
    size_t new_allocated;
    Py_ssize_t allocated = self->allocated; // 当前的容量

    // 1,容量大于个数
    // 2,个数大于容量的一半(容量足够且没有内存浪费)
    if (allocated >= newsize && newsize >= (allocated >> 1)) {
        assert(self->ob_item != NULL || newsize == 0);
        Py_SIZE(self) = newsize;
        return 0;
    }

    /* 
     * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
     */
     // 扩容机制的算法
    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;
}

四、移除元素

list.pop()
删除最后一个元素只需要修改size,不需要清除数据,下次append可以直接覆盖这个位置
指定索引位置移除后,向前补位

static PyObject *
listpop(PyListObject *self, PyObject *args)
{
    Py_ssize_t i = -1;
    PyObject *v;
    int status;

    if (!PyArg_ParseTuple(args, "|n:pop", &i))
        return NULL;

    if (Py_SIZE(self) == 0) {
        /* Special-case most common failure cause */
        PyErr_SetString(PyExc_IndexError, "pop from empty list");
        return NULL;
    }
    if (i < 0)
        i += Py_SIZE(self);
    if (i < 0 || i >= Py_SIZE(self)) {
        PyErr_SetString(PyExc_IndexError, "pop index out of range");
        return NULL;
    }
    v = self->ob_item[i];
    // 删除最后一个,仅改变size
    if (i == Py_SIZE(self) - 1) {
        status = list_resize(self, Py_SIZE(self) - 1);
        assert(status >= 0);
        return v; /* and v now owns the reference the list had */
    }
    Py_INCREF(v);
    // 不是最后一个,需要移动数据位置
    status = list_ass_slice(self, i, i+1, (PyObject *)NULL);
    assert(status >= 0);
    /* Use status, so that in a release build compilers don't
     * complain about the unused name.
     */
    (void) status;

    return v;
}

五、清空

list.clear()

static int
list_clear(PyListObject *a)
{
    Py_ssize_t i;
    PyObject **item = a->ob_item;
    if (item != NULL) {
        i = Py_SIZE(a);
        // 各个元素设置为空
        Py_SIZE(a) = 0;
        a->ob_item = NULL;
        a->allocated = 0;
        // 引用计数器-1
        while (--i >= 0) {
            Py_XDECREF(item[i]);
        }
        PyMem_FREE(item);
    }
 
    return 0;
}

六、销毁

del list

销毁列表对象的操作
将列表的引用计数-1
引用计数>0,还有应用的话不做操作
引用计数=0,没人使用

  • 处理列表的元素,将所有引用计数-1(GC回收0计数)
  • ob_item=0,ob_size=0,ob_allocated=0
  • 将列表从双向链表移除,可以销毁
  • 为了提高效率,Python结束期在内部为free_list缓存80个list,存放无使用的list,再创建的时候直接从缓存中拿来初始化。如果已经存了80个,del 的时候直接在内存中销毁对象
static void
list_dealloc(PyListObject *op)
{
    Py_ssize_t i;
    // 判断引用计数是否为0
    PyObject_GC_UnTrack(op);
    Py_TRASHCAN_SAFE_BEGIN(op)
    if (op->ob_item != NULL) {
        i = Py_SIZE(op);
        while (--i >= 0) {
            Py_XDECREF(op->ob_item[i]);
        }
        PyMem_FREE(op->ob_item);
    }
    // free_list没有80个的话缓存这个list
    if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
        free_list[numfree++] = op;
    else
        Py_TYPE(op)->tp_free((PyObject *)op);
    Py_TRASHCAN_SAFE_END(op)
}

就是说创建列表时,实际上不会直接开辟内存,而是先看看free_list

# 两次list的地址相同
>>> list1=[1,2,3]
>>> id(list1)
69070216L
>>> del list1
>>> list2=[0,0,0]
>>> id(list2)
69303304L
>>>

到此这篇关于Python源码解析之List的文章就介绍到这了,更多相关Python List内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python文件操作类操作实例详解
Jul 11 Python
Python smallseg分词用法实例分析
May 28 Python
浅谈python中set使用
Jun 30 Python
python数据结构之列表和元组的详解
Sep 23 Python
解决Python获取字典dict中不存在的值时出错问题
Oct 17 Python
Django框架模型简单介绍与使用分析
Jul 18 Python
python实现人机猜拳小游戏
Feb 03 Python
Python数组并集交集补集代码实例
Feb 18 Python
python实现按键精灵找色点击功能教程,使用pywin32和Pillow库
Jun 04 Python
Pycharm的Available Packages为空的解决方法
Sep 18 Python
python Gabor滤波器讲解
Oct 26 Python
Ubuntu20下的Django安装的方法步骤
Jan 24 Python
Python手拉手教你爬取贝壳房源数据的实战教程
matlab xlabel位置的设置方式
浏览器常用基本操作之python3+selenium4自动化测试(基础篇3)
python 实现图与图之间的间距调整subplots_adjust
Jupyter notebook 不自动弹出网页的解决方案
Python破解极验滑动验证码详细步骤
详解python字符串驻留技术
You might like
玩转虚拟域名◎+ .
2006/10/09 PHP
ecshop 订单确认中显示省市地址信息的方法
2010/03/15 PHP
php中get_meta_tags()、CURL与user-agent用法分析
2014/12/16 PHP
PHP、Python和Javascript的装饰器模式对比
2015/02/03 PHP
php array_merge_recursive 数组合并
2016/10/26 PHP
实例说明js脚本语言和php脚本语言的区别
2019/04/04 PHP
input 输入框内的输入事件详细分析
2010/03/17 Javascript
得到jQuery detach()后节点中的某个值实现代码
2013/02/05 Javascript
javascript获取隐藏元素(display:none)的高度和宽度的方法
2014/06/06 Javascript
jQuery 处理页面的事件详解
2015/01/20 Javascript
JS实现仿google、百度搜索框输入信息智能提示的实现方法
2015/04/20 Javascript
JQUERY实现网页右下角固定位置展开关闭特效的方法
2015/07/27 Javascript
jquery插件jquery.confirm弹出确认消息
2015/12/22 Javascript
JavaScript电子时钟倒计时第二款
2016/01/10 Javascript
jQuery+css实现的换页标签栏效果
2016/01/27 Javascript
easyUI实现类似搜索框关键词自动提示功能示例代码
2016/12/27 Javascript
详解AngularJs ui-router 路由的简单介绍
2017/04/26 Javascript
把JavaScript代码改成ES6语法不完全指南(分享)
2017/09/10 Javascript
细说webpack源码之compile流程-入口函数run
2017/12/26 Javascript
Vue 实现双向绑定的四种方法
2018/03/16 Javascript
解决vue.js this.$router.push无效的问题
2018/09/03 Javascript
js中this的指向问题归纳总结
2018/11/28 Javascript
echarts大屏字体自适应的方法步骤
2019/07/12 Javascript
vue+canvas实现拼图小游戏
2020/09/18 Javascript
python抓取网页图片示例(python爬虫)
2014/04/27 Python
python使用Tesseract库识别验证
2018/03/21 Python
python类的实例化问题解决
2019/08/31 Python
python图形界面开发之wxPython树控件使用方法详解
2020/02/24 Python
如何解决pycharm调试报错的问题
2020/08/06 Python
html5小技巧之通过document.head获取head元素
2014/06/04 HTML / CSS
介绍一下EJB的分类及其各自的功能及应用
2016/08/23 面试题
Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型
2013/10/30 面试题
经济与贸易专业应届生求职信
2013/11/19 职场文书
水利公司纪检监察自我鉴定
2014/02/25 职场文书
爱心募捐感谢信
2015/01/22 职场文书
2015年大学班长个人工作总结
2015/04/24 职场文书