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中的二维数组的操作方法
May 02 Python
Python列出一个文件夹及其子目录的所有文件
Jun 30 Python
Python简单检测文本类型的2种方法【基于文件头及cchardet库】
Sep 18 Python
python rsa 加密解密
Mar 20 Python
python字符串与url编码的转换实例
May 10 Python
Python中使用Counter进行字典创建以及key数量统计的方法
Jul 06 Python
Python爬虫框架scrapy实现downloader_middleware设置proxy代理功能示例
Aug 04 Python
Python3调用百度AI识别图片中的文字功能示例【测试可用】
Mar 13 Python
Python基础学习之函数方法实例详解
Jun 18 Python
python中使用while循环的实例
Aug 05 Python
Pytorch损失函数nn.NLLLoss2d()用法说明
Jul 07 Python
Python TypeError: ‘float‘ object is not subscriptable错误解决
Dec 24 Python
Python手拉手教你爬取贝壳房源数据的实战教程
matlab xlabel位置的设置方式
浏览器常用基本操作之python3+selenium4自动化测试(基础篇3)
python 实现图与图之间的间距调整subplots_adjust
Jupyter notebook 不自动弹出网页的解决方案
Python破解极验滑动验证码详细步骤
详解python字符串驻留技术
You might like
农民C键的运用技巧
2020/03/04 星际争霸
打造计数器DIY三步曲(中)
2006/10/09 PHP
PHP中extract()函数的定义和用法
2012/08/17 PHP
php实现数组筛选奇数和偶数示例
2014/04/11 PHP
PHP文件缓存类示例分享
2015/01/30 PHP
用JS实现一个页面多个css样式实现
2008/05/29 Javascript
Javascript 获取链接(url)参数的方法
2009/02/15 Javascript
JavaScript中使用正则匹配多条,且获取每条中的分组数据
2010/11/30 Javascript
基于jquery的从一个页面跳转到另一个页面的指定位置的实现代码(带平滑移动的效果)
2011/05/24 Javascript
Javascript页面跳转常见实现方式汇总
2015/11/28 Javascript
vue实现模态框的通用写法推荐
2018/02/26 Javascript
mock.js实现模拟生成假数据功能示例
2019/01/15 Javascript
vue组件数据传递、父子组件数据获取,slot,router路由功能示例
2019/03/19 Javascript
Vue数据绑定实例写法
2019/08/06 Javascript
vue+element导航栏高亮显示的解决方式
2019/11/12 Javascript
原生js实现五子棋游戏
2020/05/28 Javascript
JavaScript实现拖动对话框效果的实现代码
2020/10/12 Javascript
微信小程序调用后台service教程详解
2020/11/06 Javascript
[00:14]护身甲盾
2019/03/06 DOTA
SublimeText 2编译python出错的解决方法(The system cannot find the file specified)
2013/11/27 Python
简单介绍Python的Django框架加载模版的方式
2015/07/20 Python
Python3连接MySQL(pymysql)模拟转账实现代码
2016/05/24 Python
简单谈谈python中的Queue与多进程
2016/08/25 Python
Python两个内置函数 locals 和globals(学习笔记)
2016/08/28 Python
解决Python print输出不换行没空格的问题
2018/11/14 Python
python使用原始套接字发送二层包(链路层帧)的方法
2019/07/22 Python
Django 5种类型Session使用方法解析
2020/04/29 Python
HTML5单页面手势滑屏切换原理
2016/03/21 HTML / CSS
Vince官网:全球著名设计师品牌,休闲而优雅的服饰
2017/01/15 全球购物
怀旧收藏品和经典纪念品:Betty’s Attic
2018/08/29 全球购物
印尼网上商店:Alfacart.com
2019/03/11 全球购物
母校寄语大全
2014/04/10 职场文书
志愿者宣传口号
2014/06/17 职场文书
会议欢迎标语
2014/06/30 职场文书
入学证明
2015/06/23 职场文书
话题作文之诚信
2019/11/28 职场文书