Python列表对象实现原理详解


Posted in Python onJuly 01, 2019

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

Python列表对象实现原理详解

列表对象的创建

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列表对象实现原理详解

总结

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python多线程编程(八):使用Event实现线程间通信
Apr 05 Python
详细解读Python的web.py框架下的application.py模块
May 02 Python
Python基于Pymssql模块实现连接SQL Server数据库的方法详解
Jul 20 Python
利用aardio给python编写图形界面
Aug 21 Python
python 随机数使用方法,推导以及字符串,双色球小程序实例
Sep 12 Python
pandas实现选取特定索引的行
Apr 20 Python
在pycharm上mongodb配置及可视化设置方法
Nov 30 Python
关于Django ForeignKey 反向查询中filter和_set的效率对比详解
Dec 15 Python
python二维码操作:对QRCode和MyQR入门详解
Jun 24 Python
对YOLOv3模型调用时候的python接口详解
Aug 26 Python
PySide2出现“ImportError: DLL load failed: 找不到指定的模块”的问题及解决方法
Jun 10 Python
如何在Python3中使用telnetlib模块连接网络设备
Sep 21 Python
win8.1安装Python 2.7版环境图文详解
Jul 01 #Python
Python为何不能用可变对象作为默认参数的值
Jul 01 #Python
浅析Python与Mongodb数据库之间的操作方法
Jul 01 #Python
Python字典对象实现原理详解
Jul 01 #Python
Python Pandas 获取列匹配特定值的行的索引问题
Jul 01 #Python
Python动态语言与鸭子类型详解
Jul 01 #Python
详解python websocket获取实时数据的几种常见链接方式
Jul 01 #Python
You might like
PHP中__get()和__set()的用法实例详解
2013/06/04 PHP
Yii实现单用户博客系统文章详情页插入评论表单的方法
2015/12/28 PHP
laravel实现分页样式替换示例代码(增加首、尾页)
2017/09/22 PHP
javascript 出生日期和身份证判断大全
2008/11/13 Javascript
jquery 事件对象属性小结
2010/04/27 Javascript
js下关于onmouseout、事件冒泡的问题经验小结
2010/12/09 Javascript
jquery中ajax调用json数据的使用说明
2011/03/17 Javascript
jquery focus(fn),blur(fn)方法实例代码
2011/12/16 Javascript
Jquery方式获取iframe页面中的 Dom元素
2014/05/07 Javascript
jQuery.holdReady()方法用法实例
2014/12/27 Javascript
Javascript基础教程之数据类型 (布尔型 Boolean)
2015/01/18 Javascript
jquery实现点击弹出可放大居中及关闭的对话框(附demo源码下载)
2016/05/10 Javascript
AngularJS入门教程之XHR和依赖注入详解
2016/08/18 Javascript
JS实现非首屏图片延迟加载的示例
2018/01/06 Javascript
微信小程序日期时间选择器使用方法
2018/02/01 Javascript
使用Vuex解决Vue中的身份验证问题
2018/09/28 Javascript
新年快乐! javascript实现超级炫酷的3D烟花特效
2019/01/30 Javascript
详解JS浏览器事件循环机制
2019/03/27 Javascript
解决vue打包后vendor.js文件过大问题
2019/07/03 Javascript
Vue引入Stylus知识点总结
2020/01/16 Javascript
es6中Promise 对象基本功能与用法实例分析
2020/02/23 Javascript
python实现对一个完整url进行分割的方法
2015/04/29 Python
利用python将图片版PDF转文字版PDF
2019/05/03 Python
Python使用uuid库生成唯一标识ID
2020/02/12 Python
python程序文件扩展名知识点详解
2020/02/27 Python
Johnston & Murphy官网: 约翰斯顿·墨菲牛津总统鞋
2018/01/09 全球购物
Myprotein瑞士官方网站:运动营养和健身网上商店
2019/09/25 全球购物
经理管理专业自荐信范文
2013/12/31 职场文书
遗嘱公证书标准样本
2014/04/08 职场文书
群众路线查摆问题整改措施思想汇报
2014/10/10 职场文书
学习焦裕禄精神践行三严三实心得体会
2014/10/13 职场文书
统计员岗位职责范本
2015/04/14 职场文书
追悼词范文大全
2015/06/23 职场文书
Vue实现下拉加载更多
2021/05/09 Vue.js
nginx 添加http_stub_status_module模块
2022/05/25 Servers
html用代码制作虚线框怎么做? dw制作虚线圆圈的技巧
2022/12/24 HTML / CSS