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代码真的很爽
Aug 26 Python
详解Python中dict与set的使用
Aug 10 Python
简述Python2与Python3的不同点
Jan 21 Python
分享一下Python数据分析常用的8款工具
Apr 29 Python
Python实现的简单线性回归算法实例分析
Dec 26 Python
Python3数字求和的实例
Feb 19 Python
django drf框架中的user验证以及JWT拓展的介绍
Aug 12 Python
python使用 request 发送表单数据操作示例
Sep 25 Python
Django如何使用jwt获取用户信息
Apr 21 Python
python查看矩阵的行列号以及维数方式
May 22 Python
python数据类型强制转换实例详解
Jun 22 Python
Python3 类型标注支持操作
Jun 02 Python
Python手拉手教你爬取贝壳房源数据的实战教程
matlab xlabel位置的设置方式
浏览器常用基本操作之python3+selenium4自动化测试(基础篇3)
python 实现图与图之间的间距调整subplots_adjust
Jupyter notebook 不自动弹出网页的解决方案
Python破解极验滑动验证码详细步骤
详解python字符串驻留技术
You might like
VOLVO车载收音机
2021/03/02 无线电
PHP运行模式的深入理解
2013/06/03 PHP
浅析php静态方法与非静态方法的用法区别
2016/05/17 PHP
JQuery Tips相关(1)----关于$.Ready()
2014/08/14 Javascript
JavaScript计时器示例分析
2015/02/05 Javascript
javascript限制文本框输入值类型的方法
2015/05/07 Javascript
AngularJS初始化静态模板详解
2016/01/14 Javascript
获取layer.open弹出层的返回值方法
2018/08/20 Javascript
详解promise.then,process.nextTick, setTimeout 以及 setImmediate的执行顺序
2018/11/21 Javascript
基于layui轮播图满屏是高度自适应的解决方法
2019/09/16 Javascript
vue中get请求如何传递数组参数的方法示例
2019/11/08 Javascript
[01:07:22]2014 DOTA2华西杯精英邀请赛 5 24 DK VS VG加赛
2014/05/26 DOTA
在Python的Bottle框架中使用微信API的示例
2015/04/23 Python
Flask核心机制之上下文源码剖析
2018/12/25 Python
Python使用pyshp库读取shapefile信息的方法
2018/12/29 Python
python实现将多个文件分配到多个文件夹的方法
2019/01/07 Python
python代码编写计算器小程序
2020/03/30 Python
django 数据库连接模块解析及简单长连接改造方法
2019/08/29 Python
django自定义非主键自增字段类型详解(auto increment field)
2020/03/30 Python
浅谈tensorflow模型保存为pb的各种姿势
2020/05/25 Python
CSS3 新增选择器的实例
2019/11/13 HTML / CSS
阿迪达斯英国官方网站:adidas英国
2019/08/13 全球购物
如何转换一个字符串到enum值
2014/04/12 面试题
linux下进程间通信的方式
2014/12/23 面试题
造型师求职自荐信
2013/09/27 职场文书
销售主管岗位职责
2014/02/08 职场文书
班主任工作经验交流材料
2014/05/13 职场文书
2014年党支部承诺书
2014/05/30 职场文书
消防安全宣传口号
2014/06/10 职场文书
学院党的群众路线教育实践活动第一阶段情况汇报
2014/10/25 职场文书
2014年行政执法工作总结
2014/12/11 职场文书
社区三八妇女节活动总结
2015/02/06 职场文书
仓管员岗位职责范本
2015/04/01 职场文书
2015年大学迎新工作总结
2015/07/16 职场文书
组织委员竞选稿
2015/11/21 职场文书
Pytest之测试命名规则的使用
2021/04/16 Python