Python源码剖析2-字符串对象PyStringObject

news2024/9/21 0:39:36

 二、

1、PyStringObject与 PyString_Type

PyStringObject是变长对象中的不可变对象。当创建了一个PyStringObject对象之后,该对象内部维护的字符串就不能再被改变了。这一点特性使得 PyStringObject 对象能作为 PyDictObject 的键值,但同时也使得一些字符串操作的效率大大降低,比如多个字符串的连接操作。

[stringobject.h]
typedef struct {
	PyObject_VAR_HEAD
	long ob_shash;
	int ob_sstate;
	char ob_sval[1];
} PyStringObject;

 ob_sval是存放实际字符串的数组,数组长度是ob_size+1,因为里面存放的是原生C字符串,需要一个额外的结束符。但是注意,申请的时候令数组长度为1,实际上是为了要数组的首地址当做指针来用,ob_sval 作为首地址,在字符串申请函数中,申请的是一段长度为ob_size+1个字节的内存,而且必须满足 ob_sval[ob_size] = "\0‟。

PyStringObject 中的 ob_shash 变量其作用是缓存该对象的 HASH 值,这样可以避免每一次都重新计算该字符串对象的 HASH 值。如果一个 PyStringObject对象还没有被计算过 HASH 值,那么 ob_shash 的初始值是-1。PyStringObject 对象的 ob_sstate 变量该对象是否被 Intern 的标志。

 PyString_Type:

PyTypeObject PyString_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "str",
    PyStringObject_SIZE,
    ... 
    string_str,          /* tp_str*/     //tp_str 指向string_str 函数
    &string_as_number,   /* tp_as_number */
    &string_as_sequence, /* tp_as_sequence */
    &string_as_mapping,  /* tp_as_mapping */
    (hashfunc)string_hash,
    string_methods,
    ....
    string_new,       //实例化对象方法   /* tp_new */
    PyObject_Del,        /* tp_free */
};

如图,tp_itemsize被设置为sizeof(char),对于python中任意变长对象,tp_itemsize这个域必须设置,它指明了由变长对象保存的元素的单位长度,tp_itemsize和ob_size共同决定了还需要额外申请的内存大小。

需要注意的是,字符串类型对象的tp_as_number,tp_as_sequence,tp_as_mapping,三个域都被设置了。这表示PyStringObject对数值操作,序列操作和映射操作都支持。

2、PyStringObject对象创建

PyObject *PyString_FromString(const char *str)
{
	register size_t size;
	register PyStringObject *op;
 
	assert(str != NULL);
    【1】:判断字符串长度
	size = strlen(str);
	if (size > PY_SSIZE_T_MAX - sizeof(PyStringObject)) {
		PyErr_SetString(PyExc_OverflowError,
			"string is too long for a Python string");
		return NULL;
	}
    【2】:处理null string
	if (size == 0 && (op = nullstring) != NULL) {
#ifdef COUNT_ALLOCS
		null_strings++;
#endif
		Py_INCREF(op);
		return (PyObject *)op;
	}
    【3】:处理单字符,从缓存池中获取
	if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
#ifdef COUNT_ALLOCS
		one_strings++;
#endif
		Py_INCREF(op);
		return (PyObject *)op;
	}
    【4】:创建新的PyStringObject对象,并初始化
	/* Inline PyObject_NewVar */
	op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
	if (op == NULL)
		return PyErr_NoMemory();
	PyObject_INIT_VAR(op, &PyString_Type, size);
	op->ob_shash = -1;
	op->ob_sstate = SSTATE_NOT_INTERNED;
	Py_MEMCPY(op->ob_sval, str, size+1);
	.........
	return (PyObject *) op;
}

 【1】判断字符串长度,字符串长度如果过长,将不会创建并返回空。win32 平台下,该值是2147483647。

【2】第一次在一个空字符串基础上创建一个PyStringObject,由于nullstring指针被初始化为NULL,所以python会为这个空字符串建立一个PyStringObject对象,将这个PyStringObject通过intern机制进行共享,然后nullstring指向这个被共享的对象。

 【4】申请内存,还为字符串数组内的元素申请额外内存,然后将hash值设为-1,将intern标志设置为SSTATE_NOT_INTERNED。最后将str指向的字符串数组拷贝到PyStringObject所维护的空间中。如“Python”PyStringObject对象在内存中的状态:

3、 Intern机制

intern机制的目的是:对于被intern之后的对象,在python运行期间,系统中只有唯一一个与之相对应的PyStringObject对象。当判断两个PyStringObject对象是否相同时,如果他们都被intern了,那么只需要简单检查他们对应的PyObject*是否相同即可。这个机制既节省空间,又简化了PyStringObject对象的比较。
当对一个   PyStringObject对象进行intern处理时,首先在interned这个dict中检查是否有满足条件(b中维护的字符串与a相同)的对象b,如果存在,那么指向a的PyObject指针指向b,而a的引用计数-1,b的引用计数+1. 

对于被intern机制处理过的PyStringObject对象,python采用特殊的计数机制。在将一个PyStringObject对象a的PyObject作为key和value添加到interned中,此时a的引用计数进行了两次+1,由于设计者规定interned中的a指针不能被视为有效引用,所以在代码【3】处a的计数器-2,否则删除a是永远不可能的。

4、字符缓冲池

python为整数准备了整数对象池,python为字符类型也准备了字符缓冲池。python设计者为PyStringObject设计了一个对象池characters。

首先对创建的字符对象进行intern操作,再将intern的结果缓存到字符缓冲池characters中。如下图演示缓存一个字符对应的PyStringObject对象的过程:

  1. 创建PyStringObject对象p ;
  2. 对p进行intern操作;
  3. 将p缓存到字符缓冲池中

5、PyStringObject 效率问题

 python中可以通过+进行字符串拼接,但是性能及其低下,由于PyStringObject是不可变对象,这意味着在拼接时必须新建一个PyStringObject对象。这样如果连接N个PyStringObject对象,需要N-1次内存的申请工作,这无疑严重影响python的执行效率。

static PyObject *
string_join(PyStringObject *self, PyObject *orig)
{
	char *sep = PyString_AS_STRING(self);
	const Py_ssize_t seplen = PyString_GET_SIZE(self);
	PyObject *res = NULL;
	char *p;
	Py_ssize_t seqlen = 0;
	size_t sz = 0;
	Py_ssize_t i;
	PyObject *seq, *item;
 
	seq = PySequence_Fast(orig, "");
	 .....
    【1】遍历list中每个字符串,累加获取所有字符串长度
 
	for (i = 0; i < seqlen; i++) {
		const size_t old_sz = sz;
		item = PySequence_Fast_GET_ITEM(seq, i);
		
		sz += PyString_GET_SIZE(item);
		if (i != 0)
			sz += seplen;
		
	}
     创建长度为sz的PyStringObject对象
	res = PyString_FromStringAndSize((char*)NULL, sz);
	if (res == NULL) {
		Py_DECREF(seq);
		return NULL;
	}
   将list中的字符串拷贝到新建的PyStringObject对象中
	p = PyString_AS_STRING(res);
	for (i = 0; i < seqlen; ++i) {
		size_t n;
		item = PySequence_Fast_GET_ITEM(seq, i);
		n = PyString_GET_SIZE(item);
		Py_MEMCPY(p, PyString_AS_STRING(item), n);
		p += n;
		if (i < seqlen - 1) {
			Py_MEMCPY(p, sep, seplen);
			p += seplen;
		}
	}
 
	Py_DECREF(seq);
	return res;
}

执行join操作时,会先统计list中有多少个PyStringObject对象,并统计这里PyStringObject所维护的字符串总长度,然后申请内存,将list中的所有PyStringObject对象维护的字符串拷贝到新开辟的内存空间中。N个PyStringObject对象拼接join只需申请一次内存,比+操作节约了N-2次操作,效率的提升非常明显。
 

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

参考:

  • Python源码剖析(陈孺)

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

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

相关文章

Kafka RecordAccumulator 三 高并发写入数据

Kafka RecordAccumulator 三 高并发写入数据 首先我们客户端会通过多线程的方式来发送消息&#xff08;一般业务需求可能会通过业务系统或者大数据流计算系统如Spark Streaming或者Flink将业务数据发送出去&#xff0c;进而让下游系统消费使用&#xff09;&#xff0c;那这里业…

【Linux】进程状态|僵尸进程 |孤儿进程

索引运行状态&#xff1a;阻塞状态挂起状态看看Linux是怎么做的运行状态R睡眠状态S停止状态T两个特殊的进程&#xff1a;僵尸进程孤儿进程在之前我们听过很多很多进程的状态&#xff0c;像是运行、新建、就绪、挂起、阻塞、等待、停止、挂机、死亡等等。推荐阅读&#xff1a;通…

http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码

目录 1.digest认证是什么&#xff1f; 2.digest认证过程 3.digest认证参数详解 4.基于SpringBoot实现digest认证 5.digest认证演示 6.digest认证完整项目 7.参考博客 1.digest认证是什么&#xff1f; HTTP通讯采用人类可阅读的文本格式进行数据通讯&#xff0c;其内容非…

Android入门第40天-Android中的Service(SimpleStartService)

简介 博文总阅读量已经突破了300万&#xff0c;给自己加油打CALL。 从今天开始&#xff0c;之前39天的Android如果每一篇只有30分钟就能读完和掌握那么从今天开始越往后会越复杂。因为我们的Android教程开始进入“中级”难度了。特别是Service&#xff0c;这个Service我要分成…

java面向对象的三大特性之封装和继承(配视频讲解)

&#x1f345;程序员小王的博客&#xff1a;程序员小王的博客 &#x1f345;程序员小王的资源博客&#xff1a;http://wanghj.online/ &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 如有编辑错误联系作者&#xff0c;如果有比较好的文章欢迎…

JMeter入门教程(13) --事务

文章目录1.任务背景2.任务目标3.任务实操3.1.1 事务控制器3.2.2循环控制器1.任务背景 JMeter中的事务是通过事务控制器实现的。&#xff0c;为了衡量服务器对某一个或一系列操作处理的响应时间&#xff0c;需要定义事务。下面我们详细介绍在JMeter中如何使用事务 2.任务目标 …

基于JSP的网络教学平台的设计与实现

目 录 摘 要 I Abstract II 一、 引言 1 &#xff08;一&#xff09;项目开发的背景 1 &#xff08;二&#xff09;项目开发的意义 1 二、可行性分析及总体设计原则 3 &#xff08;一&#xff09;可行性分析 3 1&#xff0e;技术可行性 3 2&#xff0e;经济可行性 3 3&#xff…

MATLAB算法实战应用案例精讲-【图像处理】目标检测(附实战案例及代码实现)

前言 目标检测,也叫目标提取,是一种基于目标几何和统计特征的图像分割。它将目标的分割和识别合二为一,其准确性和实时性是整个系统的一项重要能力。尤其是在复杂场景中,需要对多个目标进行实时处理时,目标自动提取和识别就显得特别重要。 随着计算机技术的发展和计算机视…

Servlet —— Tomcat, 初学 Servlet 程序

JavaEE传送门JavaEE HTTP —— HTTP 响应详解, 构造 HTTP 请求 HTTPS —— HTTPS的加密方式 目录TomcatServlethello world创建项目引入 Servlet 依赖创建目录结构编写代码doGet打包程序部署程序验证程序Tomcat Tomcat 是一个 HTTP 服务器 HTTP 客户端, 就是大家平时用到的浏…

大数据hadoop_HDFS概述(1)

文章目录1. HDFS概述1.1 HDFS背景1.2 定义2. HDFS优缺点2.1 优点2.2 缺点3. HDFS架构4. HDFS文件块大小1. HDFS概述 1.1 HDFS背景 面对今天的互联网公司&#xff0c;每天都会有上亿次的用户访问量&#xff0c;用户每进行一次操作&#xff0c;都会产生数据&#xff0c;面对传统…

Android入门第41天-Android中的Service(bindService)

介绍 在前一天我们介绍了Android中有两种启动Service的方法。并擅述了startService和bindService的区别。同时我们着重讲了startService。 因此今天我们就来讲bindService。bindService大家可以认为它是和Android的一个共生体。即这个service所属的activity如果消亡那么bindS…

Docker涉及的Linux命名空间、CGroups

概述 Linux的NameSpace介绍 很多编程语言都包含了命名空间的概念&#xff0c;我们可以认为命名空间是一种封装&#xff0c;封装本身实现了代码的隔离。在操作系统中命名空间提供的是系统资源的隔离&#xff0c;其中系统资源包括了&#xff1a;进程、网络、文件系统…实际上li…

《模拟电子技术》半导体原理部分笔记

《模拟电子技术》笔记绪论第一章 常用半导体器件第二章 基本放大电路绪论 有的人把三极管的出现作为电子技术工业革命的开始标志学习架构&#xff1a;半导体器件&#xff08;二极管、三极管、场效应晶体管&#xff09;、基于上述管的放大电路、集成运算放大器、放大电路的频率…

第11章 初识IdentityServer4

1 构建IdentityServer4 服务 1.1 通过配置类配置类(Config)实例化IdentityServer4中间件 using IdentityServer4.Models; namespace BuilderServer { /// <summary> /// 【配置--类】 /// <remarks> /// 摘要&#xff1a; /// 通过该中类的方法成员&#xff…

如何给firefox和google chrome鼠标手势

背景 已经习惯了有鼠标手势&#xff0c;因为一天到晚都在浏览器上查询资料&#xff0c;所以必须把这个鼠标手势设置好。 firefox 搜索Foxy Gestures然后安装 google chrome crxMouse Chrome 点击google浏览器上的扩展程序图标&#xff0c;然后点击管理扩展程序&#xff1a…

游泳耳机哪个牌子好、分享几款游泳听音乐最好的耳机推荐

如今,水上运动爱好者越来越多了,无论是游泳,还是冲浪早已成为了我们很多人经常参加的运动项目。不过他们都抱怨过类似的问题——可以在水上运动中使用的无线耳机实在是太少了。防水性能达不到可游泳级别,不带内存需要配备手机使用,这些都是造成耳机无法在水上使用的原因。今天小…

【matplotlib】1-使用函数绘制图表

文章目录使用函数绘制图表1.绘制matplotlib图表组成元素的主要函数2.准备数据3.函数用法3.1函数plot()--展现变量的趋势变化3.2函数scatter()--寻找变量之间的关系3.3函数xlim()--设置x轴的数值显示范围3.4函数xlabel()--设置x轴的标签文本3.5 函数grid()--绘制刻度线的网格线3…

Kong(三)Konga UI安装和使用

一 konga 的github地址 konga安装参考 ① Kong 可视化UI 选择 官方kong-dashboard 1&#xff09;收费&#xff1a;当前kong的社区版是没有dashboard的,但是付费的企业版是有带的2&#xff09;kong-dashboard最新版本v3.6.0,只支持到kongv0.14.x,对于更高的kong版本,功能支…

SpringBoot整合RabbitMQ,实现单机抢票系统

MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法。MQ是消费-生产者模型的一个典型的代表&#xff0c;一端往消息队列中不断写入消息&#xff0c;而另一端则可以读取队列中的消息。 消息中间件最主要的作用是解耦&#xff0c;中间…

C与C++中的常用符号与标点用法详解及实例

C与C中的常用符号与标点符号有&#xff1a;“”、“-”、“*”、 “/”、“%”、“&”、“\”、“|”、“~”、“^”、“&”、“|”、“&#xff01;”、“>”、“<”、""、“#”、“&#xff1f;”、“&#xff0c;”、“.”、“&#xff1a;”、单引…