Python源码剖析3-列表对象PyListObject

news2024/12/25 13:34:51

1、PyListObject对象

PyListObject 对象可以有效地支持插入,添加,删除等操作,在 Python 的列表中,无一例外地存放的都是 PyObject 的指针。所以实际上,你可以这样看待 Python 中的列表: vector<PyObject*>

[listobject.h]
typedef struct {
	PyObject_VAR_HEAD
	/* Vector of pointers to list elements. list[0] is ob_item[0],etc.*/
	PyObject **ob_item;
	int allocated;
} PyListObject;

PyObject_VAR_HEAD中的 ob_size 和 allocated 都和内存的管理有关,类似于C++ 中 vector 的 size 和 capacity ,allocated存放的是当前已经申请到的内存数量,而ob_size是实际存储的元素个数。因此一定满足:

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

ob_item指针指向元素列表所在内存块首地址。

2、PyListObject对象的创建和维护

Python 中只提供了唯一一种创建 PyListObject 对象的方法 PyList_New。而且与想象中不一样的是,该方法仅仅制定了元素的个数而没有指定元素的具体内容。

[listobject.c]
PyObject* PyList_New(int size)
{
	PyListObject *op;
	size_t nbytes;
	nbytes = size * sizeof(PyObject *);

	// 获得新的 PyListObject 对象的指针
	if (num_free_lists) {
		//使用缓冲池
		num_free_lists--;
		op = free_lists[num_free_lists];
		_Py_NewReference((PyObject *)op); // 调整引用计数
	}
    else {
		//缓冲池中没有可用的对象,创建对象
		op = PyObject_GC_New(PyListObject, &PyList_Type);
	}
    
	//为 PyListObject 对象中维护的元素指针数组申请空间
	if (size <= 0)
		op->ob_item = NULL;
	else {
		op->ob_item = (PyObject **) PyMem_MALLOC(nbytes);
		memset(op->ob_item, 0, nbytes);
	}
	op->ob_size = size;
	op->allocated = size;
	_PyObject_GC_TRACK(op);
	return (PyObject *) op;
}

首先,注意到PyListObject同样使用了缓冲池,和前面一样,缓冲池中全部是对象的指针,也就是说节省了对PyListObject这个结构的所占用的内存的反复创建与回收。在创建PyListObject时,会先检查缓冲池free_list中是否有可用的对象。如果有,则直接使用,否则通过PyObject_GC_New咋系统堆中申请内存,创建新的PyListObject。

默认情况下,freelist 列表中会缓存80个对象指针。

#define PyList_MAXFREELIST 80
#endif
static PyListObject *free_list[PyList_MAXFREELIST];
static int numfree = 0;

其次,列表对象实际上分为两部分,一是PyListObject对象本身,二是PyListObject对象维护的元素列表,这是两块分离的内存,通过ob_item建立联系。这是一块与PyListtObject对象分离的内存,通过PyObject**类型的op->ob_item连接,初始化中仅仅是使其比特位全0。

最后,就是新初始化的数组的ob_size和allocated是同一个值。

3、设置元素

[listobject.c]
int PyList_SetItem(register PyObject *op, register int i, register
PyObject *newitem)
{
	register PyObject *olditem;
	register PyObject **p;
	if (!PyList_Check(op)) {
		......
	}
	if (i < 0 || i >= ((PyListObject *)op) -> ob_size) {
		Py_XDECREF(newitem);
		PyErr_SetString(PyExc_IndexError, "list assignment index out of range");
		return -1;
	}
	p = ((PyListObject *)op) -> ob_item + i;
	olditem = *p;
	*p = newitem;
	Py_XDECREF(olditem);
	return 0;
}

 首先是进行类型检查,然后进行索引的有效性检查,当类型和索引检查通过 后,将待加入的 PyObject 指针放到指定的位置,然后将这个位置原来存放的对象的引用计数减 1。这里的 olditem 很可能会是 NULL,比如向一个新创建的 PyListObject 对象加入元素,就会碰到这样的情况,所以这里必须使用 Py_XDECREF。

4、插入元素

调用PyList_Insert函数,实际是调用内部的ins1函数

[listobject.c]
int PyList_Insert(PyObject *op, int where, PyObject *newitem)
{
	......//类型检查
	return ins1((PyListObject *)op, where, newitem);
}

static int ins1(PyListObject *self, int where, PyObject *v)
{
	int i, n = self->ob_size;
	PyObject **items;
	......
    //注意这里的resize
	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;
}

插入流程:

  1. 检查是否为空值;
  2. 检查allocated是否已经达到系统定义的最大值;
  3. 对负数游标进行转换;
  4. 对插入元素右边元素右移一位;

resize函数:

[listobject.c]
static int list_resize(PyListObject *self, int newsize)
{
	PyObject **items;
	size_t new_allocated;
	int allocated = self->allocated;
	/* Bypass realloc() when a previous overallocation is large enough to accommodate the newsize. If the newsize falls lower than half the allocated size, then proceed with the realloc() to shrink the list. */
	if (allocated >= newsize && newsize >= (allocated >> 1)) {
		assert(self->ob_item != NULL || newsize == 0);
		self->ob_size = newsize;
		return 0;
	}
	/* This over-allocates proportional to the list size, making room
	 * for additional growth. The over-allocation is mild, but is
	 * enough to give linear-time amortized behavior over a long
	 * sequence of appends() in the presence of a poorly-performing
	 * system realloc().
	 * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
	 */
    // 新的内存大小
	new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize;
	if (newsize == 0)
		new_allocated = 0;
	items = self->ob_item;
	PyMem_RESIZE(items, PyObject *, new_allocated);

	self->ob_item = items;
	self->ob_size = newsize;
	self->allocated = new_allocated;
	return 0;
}

 分四种情况处理:

  1. newsize > ob_size && newsize < allocated :简单调整 ob_size 值。
  2. newsize < ob_size && newsize > allocated/2 :简单调整 ob_size 值。
  3. newsize < ob_size && newsize < allocated/2 :当前空间过大,收缩空间。
  4. newsize > ob_size && newsize > allocated :存货不足,继续申请。

append操作其实就是先使用list_resize让现有元素数加 1 ,然后使用PyList_SetItem设置最后一个元素的值。

5、删除元素 

[listobject.c]
static PyObject * listremove(PyListObject *self, PyObject *v)
{
	int i;
	for (i = 0; i < self->ob_size; i++) {
		int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
		if (cmp > 0) {
			if (list_ass_slice(self, i, i+1,(PyObject *)NULL) == 0)
			Py_RETURN_NONE;
			return NULL;
		}
		else if (cmp < 0)
			return NULL;
	}
	PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list");
	return NULL;
}

[listobject.h]
static int list_ass_slice(PyListObject *a, int ilow, int ihigh, PyObject *v);

 在遍历 PyListObject 中所有元素的过程中,将待删除的元素与 PyListObject 中的每个元素一一进行比较,比较操作是通过 PyObject_RichCompareBool 完成的。如果发现了匹配,则调用 list_ass_slice 进行删除操作。而list_ass_slice函数则是实现了切片功能,当传入的v是NULL时,实际效果就是将被切的部分删除。在该函数内部,memmove 通过搬移内存+来达到删除元素的目的。

6、PyListObject 对象缓冲池

    在创建一个新list时,我们可以看到创建过程分为两部,PyListObject对象和维护的元素列表。与之对应,在销毁一个list时,销毁过程也分离,先销毁PyListObject维护的元素列表,再释放PyListObject自身。

[listobject.c]
static void list_dealloc(PyListObject *op)
{
	int i;
    //调整 list 中对象的引用计数
	if (op->ob_item != NULL) {
		/* Do it backwards, for Christian Tismer. There's a simple test case where somehow this reduces thrashing when a *very* large list is created and immediately deleted. */
		i = op->ob_size;
		while (--i >= 0) {
			Py_XDECREF(op->ob_item[i]);
		}
		PyMem_FREE(op->ob_item);
	}
    //将被销毁的 PyListObject 对象放入缓冲池
	if (num_free_lists < MAXFREELISTS && PyList_CheckExact(op))
		free_lists[num_free_lists++] = op;
	else
		op->ob_type->tp_free((PyObject *)op);
}

在删除PyListObject自身时,python会先检查缓冲池free_list,查看其中缓冲的PyListObject对象是否满了,如果没有,就将该PyListObject对象放在缓冲池中,以备后用

删除的时候,使用倒序将op->ob_size中的对象的引用计数全部减一,释放op->ob_size的空间,然后尝试将该对象PyListObject结构体放入缓冲池。注意没有将ob_sizeallocated置0,因为下次从缓冲池取用的时候会为其赋新值。


————————————————

参考:

  • Python源码剖析(陈孺)


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

卡尔曼滤波之最优状态估计和最优状态估计算法

1. 最优状态估计 情景1&#xff1a;假设一个一个比赛中&#xff0c;不同队伍的自动驾驶汽车使用 GPS 定位&#xff0c;在 100 种不同的地形上各行驶 1 公里。每次都尽可能停在终点。然后计算每只队伍的平均最终位置。 第一组输了&#xff1a;因为虽然方差小&#xff0c;但是偏差…

基于java+ssm+vue+mysql的社区流浪猫狗救助网站

项目介绍 随着迅速的发展&#xff0c;宠物饲养也较以前发生很大的变化&#xff0c;社区流浪猫狗救助网站系统以其独有的优势脱颖而出。“社区流浪猫狗救助网站”是以JAVA程序设计语言课为基础的设计出适合社区流浪猫狗救助网站&#xff0c;其开发过程主要包括后台数据库的建立…

Vulnhub:Os-Bytesec靶机渗透

目录 前期准备&#xff1a; 渗透过程 测试SMB PATH环境变量介绍 前期准备&#xff1a; 1.在vulnhub官网下载靶机&#xff1a;Os-Bytesec。 2.靶机用virtualbox打开&#xff0c;网络使用仅主机模式&#xff0c;攻击机kali在vmware上使用桥接模式&#xff0c;并桥接到virtru…

性能优化:Netty连接参数优化

参考资料&#xff1a; 《Netty优化》 相关文章&#xff1a; 《Netty&#xff1a;入门&#xff08;1&#xff09;》 《Netty&#xff1a;入门&#xff08;2&#xff09;》 《Netty&#xff1a;粘包与半包的处理》 《性能优化&#xff1a;TCP连接优化之三次握手》 写在开头…

JavaWeb_第5章_会话技术_Cookie+Session

JavaWeb_第5章_会话技术_CookieSession 文章目录JavaWeb_第5章_会话技术_CookieSession1&#xff0c;会话跟踪技术的概述2&#xff0c;Cookie2.1 Cookie的基本使用2.2 Cookie的原理分析2.3 Cookie的使用细节2.3.1 Cookie的存活时间2.3.2 Cookie存储中文3&#xff0c;Session3.1…

vs2019_qt6.2.4_dcmtk3.6.7_vtk9.2.2_itk5.3_opencv4.6.0编译记录

目录 1 dcmtk3.6.7编译 2 vtk9.2.2编译 3 itk5.3编译 4 opencv4.6.0 5 参考链接 编译顺序&#xff0c;qt6.2.4下载----->dcmtk3.6.7----->vtk9.2.2----->itk5.3----->opencv4.6.0 opencv4.6需要使用到vtk9.2.2&#xff0c;需要在最后编译。 opencv遇到…

[附源码]计算机毕业设计校园代取快递系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Nacos2.1.2源码修改支持高斯,postresql

1、下载代码 git clone https://github.com/alibaba/nacos.git -b 2.1.2 或 git clone https://github.com/alibaba/nacos.git 2、maven命令执行下试试能不能打包 mvn -Prelease-nacos -Dmaven.test.skiptrue -Drat.skiptrue clean install -U 或 mvn -Prelease-nacos ‘-Dmav…

盒子模型详解

菜鸟教程解释&#xff1a;所有HTML元素可以看作盒子 1.普通盒子模型&#xff1a; margin&#xff08;外边距&#xff09;&#xff1a;清除边框外的区域&#xff0c;外边距是透明的&#xff0c;不包含在background属性中。 border&#xff08;边框&#xff09;&#xff1a; 围绕…

JUC并发编程与源码分析笔记06-Java内存模型之JMM

计算机硬件存储体系 CPU的运行并不是直接操作内存而是先把内存里边的数据读到缓存&#xff0c;而内存的读和写操作的时候就会造成不一致的问题。 JVM规范中试图定义一种Java内存模型&#xff08;Java Memory Model&#xff0c;简称JMM&#xff09;来屏蔽掉各种硬件和操作系统的…

电平触发的触发器

普通的SR锁存器没有任何抗干扰能力 我们要加控制信号&#xff0c;来抵抗干扰 比如说我们不把信号直接加在门上&#xff0c;我们可以再加一级门电路&#xff0c;让这个输出和输入不在同一个门上&#xff0c;我们希望加入一个控制信号&#xff0c;来控制电路工作的时刻 对电路结…

神经网络——反向传播算法

一、多元分类 之前讨论的神经网络都是以二元分类为目的进行介绍的。 当我们有不止两种分类时&#xff08;也就是y1,2,3….y1,2,3….y1,2,3….&#xff09;&#xff0c;比如以下这种情况&#xff0c;该怎么办&#xff1f;如果我们要训练一个神经网络算法来识别路人、汽车、摩托…

mysql的主从复制与读写分离

目录 一. MySQL 主从复制原理 1.1 MySQL 支持的复制类型 1.2 MySQL主从复制的工作过程 二、主从复制实验部署 2.1、实验环境 2.2、mysql主从服务器时间同步 主服务器&#xff08;192.168.80.11&#xff09; 从服务器&#xff08;192.168.80.12/13&#xff09; 2.3、主…

[附源码]计算机毕业设计网上鲜花购物系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【机器学习】支持向量回归

有任何的书写错误、排版错误、概念错误等&#xff0c;希望大家包含指正。 在阅读本篇之前建议先学习&#xff1a; 【机器学习】支持向量机【上】硬间隔 【机器学习】支持向量机【下】软间隔与核函数 支持向量回归 支持向量回归&#xff08;support vector regression&#xf…

[附源码]计算机毕业设计基于SpringBoot的党务管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

《人类简史》笔记三—— 历史从无正义

目录 一、尽管把人人生而平等喊得震天响&#xff0c;其实还是把人分成了上下等级 二、恶性循环 三、当男人究竟有什么好的&#xff1f; 一、尽管把人人生而平等喊得震天响&#xff0c;其实还是把人分成了上下等级 古时候&#xff1a; 上等人 平民和奴隶 现在&#xff1a;…

网络结构模式,协议,端口,网络模型,arp

网络结构模式&#xff08;软件结构&#xff09; C/S结构 服务器 - 客户机&#xff0c;即 Client - Server&#xff08;C/S&#xff09;结构 C/S 结构通常采取两层结构: 服务器负责数据的管理客户机负责完成与用户的交互任务 在C/S结构中&#xff0c;应用程序分为两部分: 服务…

koa 和 express 的对比

前言 天冷了&#xff0c;唯有学习来温暖自己。 最近利用业余的时间&#xff0c;跟着 coderwhy 老师学习 node.js&#xff0c;了解以及掌握一些服务端的常见知识&#xff1a; fileSystem&#xff1a;文件读取模块。events&#xff1a;事件流Buffer&#xff1a;node 中处理二进…

高仿英雄联盟游戏网页制作作业 英雄联盟LOL游戏HTML网页设计模板 简单学生网页设计 静态HTML CSS网站制作成品

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…