Python字符串对象实现原理详解


Posted in Python onJuly 01, 2019

在Python世界中将对象分为两种:一种是定长对象,比如整数,整数对象定义的时候就能确定它所占用的内存空间大小,另一种是变长对象,在对象定义时并不知道是多少,比如:str,list, set, dict等。

>>> import sys
>>> sys.getsizeof(1000)
28
>>> sys.getsizeof(2000)
28
>>> sys.getsizeof("python")
55
>>> sys.getsizeof("java")
53

如上,整数对象所占用的内存都是28字节,和具体的值没关系,而同样都是字符串对象,不同字符串对象所占用的内存是不一样的,这就是变长对象,对于变长对象,在对象定义时是不知道对象所占用的内存空间是多少的。

字符串对象在Python内部用PyStringObject表示,PyStringObject和PyIntObject一样都属于不可变对象,对象一旦创建就不能改变其值。(注意:变长对象和不可变对象是两个不同的概念)。PythonStringObject的定义:

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

不难看出Python的字符串对象内部就是由一个字符数组维护的,在整数的实现原理一文中提到PyObject_HEAD,对于PyObject_VAR_HEAD就是在PyObject_HEAD基础上多出一个ob_size属性:

[object.h]
#define PyObject_VAR_HEAD  
 PyObject_HEAD   
 int ob_size; /* Number of items in variable part */
typedef struct {
 PyObject_VAR_HEAD
} PyVarObject;
  • ob_size保存了变长对象中元素的长度,比如PyStringObject对象"Python"的ob_size为6。
  • ob_sval是一个初始大小为1的字符数组,且ob_sval[0] = '\0',但实际上创建一个PyStringObject时ob_sval指向的是一段长为ob_size+1个字节的内存。
  • ob_shash是字符串对象的哈希值,初始值为-1,在第一次计算出字符串的哈希值后,会把该值缓存下来,赋值给ob_shash。
  • ob_sstate用于标记该字符串对象是否进过intern机制处理(后文会介绍)。

PYSTRINGOBJECT对象创建过程

[stringobject.c]
PyObject * PyString_FromString(const char *str)
{
register size_t size;
register PyStringObject *op;
assert(str != NULL);
size = strlen(str);
// [1]
if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {
PyErr_SetString(PyExc_OverflowError,
"string is too long for a Python string");
return NULL;
}
// [2]
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]
/* Inline PyObject_NewVar */
op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + 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);
/* share short strings */
if (size == 0) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
nullstring = op;
Py_INCREF(op);
} else if (size == 1) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
}
return (PyObject *) op;
}
  • 如果字符串的长度超出了Python所能接受的最大长度(32位平台是2G),则返回Null。
  • 如果是空字符串,那么返回特殊的PyStringObject,即nullstring。
  • 如果字符串的长度为1,那么返回特殊PyStringObject,即onestring。
  • 其他情况下就是分配内存,初始化PyStringObject,把参数str的字符数组拷贝到PyStringObject中的ob_sval指向的内存空间。

字符串的INTERN机制

PyStringObject的ob_sstate属性用于标记字符串对象是否经过intern机制处理,intern处理后的字符串,比如"Python",在解释器运行过程中始终只有唯一的一个字符串"Python"对应的PyStringObject对象。

>>> a = "python"
>>> b = "python"
>>> a is b
True

如上所示,创建a时,系统首先会创建一个新的PyStringObject对象出来,然后经过intern机制处理(PyString_InternInPlace),接着查找经过intern机制处理的PyStringObject对象,如果发现有该字符串对应的PyStringObject存在,则直接返回该对象,否则把刚刚创建的PyStringObject加入到intern机制中。由于a和b字符串字面值是一样的,因此a和b都指向同一个PyStringObject("python")对象。那么intern内部又是一个什么样的机制呢?

[stringobject.c]
static PyObject *interned;
void PyString_InternInPlace(PyObject **p)
{
register PyStringObject *s = (PyStringObject *)(*p);
PyObject *t;
if (s == NULL || !PyString_Check(s))
Py_FatalError("PyString_InternInPlace: strings only please!");
/* If it's a string subclass, we don't really know what putting
it in the interned dict might do. */
// [1]
if (!PyString_CheckExact(s))
return;
// [2]
if (PyString_CHECK_INTERNED(s))
return;
// [3]
if (interned == NULL) {
interned = PyDict_New();
if (interned == NULL) {
PyErr_Clear(); /* Don't leave an exception */
return;
}
}
t = PyDict_GetItem(interned, (PyObject *)s);
if (t) {
Py_INCREF(t);
Py_DECREF(*p);
*p = t;
return;
}
if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {
PyErr_Clear();
return;
}
/* The two references in interned are not counted by refcnt.
The string deallocator will take care of this */
Py_REFCNT(s) -= 2;
PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}

1.先类型检查,intern机制只处理字符串

2.如果该PyStringObject对象已经进行过intern机制处理,则直接返回

3.interned其实一个字典对象,当它为null时,初始化一个字典对象,否则,看该字典中是否存在一个key为(PyObject *)s的value,如果存在,那么就把该对象的引用计数加1,临时创建的那个对象的引用计数减1。否则,把(PyObject *)s同时作为key和value添加到interned字典中,与此同时它的引用计数减2,这两个引用计数减2是因为被interned字典所引用,但这两个引用不作为垃圾回收的判断依据,否则,字符串对象永远都不会被垃圾回收器收集了。

Python字符串对象实现原理详解

上述代码中,给b赋值为"python"后,系统中创建了几个PyStringObject对象呢?答案是:2,在创建b的时候,一定会有一个临时的PyStringObject作为字典的key在interned中查找是否存在一个PyStringObject对象的值为"python"。

字符串的缓冲池

字符串除了有intern机制缓存字符串之外,字符串还有一种专门的短字符串缓冲池characters。用于缓存字符串长度为1的PyStringObject对象。

static PyStringObject *characters[UCHAR_MAX + 1]; //UCHAR_MAX = 255

创建长度为1的字符串时流程:

...
else if (size == 1) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
  • 首先创建一个PyStringObject对象。
  • 进行intern操作
  • 将PyStringObject缓存到characters中
  • 引用计数增1

Python字符串对象实现原理详解

总结:

1. 字符串用PyStringObject表示

2. 字符串属于变长对象

3. 字符串属于不可变对象

4. 字符串用intern机制提高python的效率

5. 字符串有专门的缓冲池存储长度为1的字符串对象

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

Python 相关文章推荐
跟老齐学Python之编写类之一创建实例
Oct 11 Python
python实现图像识别功能
Jan 29 Python
Python中 传递值 和 传递引用 的区别解析
Feb 22 Python
python获取指定字符串中重复模式最高的字符串方法
Jun 29 Python
浅析python参数的知识点
Dec 10 Python
在macOS上搭建python环境的实现方法
Aug 13 Python
python opencv调用笔记本摄像头
Aug 28 Python
使用浏览器访问python写的服务器程序
Oct 10 Python
基于python实现语音录入识别代码实例
Jan 17 Python
Python参数传递及收集机制原理解析
Jun 05 Python
Python基于pyjnius库实现访问java类
Jul 31 Python
Python基于unittest实现测试用例执行
Nov 25 Python
Python转换时间的图文方法
Jul 01 #Python
Python列表对象实现原理详解
Jul 01 #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
You might like
Laravel使用memcached缓存对文章增删改查进行优化的方法
2016/10/08 PHP
yii2中关于加密解密的那些事儿
2018/06/12 PHP
JS图片浏览组件PhotoLook的公开属性方法介绍和进阶实例代码
2010/11/09 Javascript
Javascript面向对象设计一 工厂模式
2011/12/20 Javascript
javascript中的void运算符语法及使用介绍
2013/03/10 Javascript
javascript实现回到顶部特效
2015/05/06 Javascript
jQuery获取URL请求参数的方法
2015/07/18 Javascript
JavaScript编写带旋转+线条干扰的验证码脚本实例
2016/05/30 Javascript
jQuery3.0中的buildFragment私有函数详解
2016/08/16 Javascript
JS实现的图片预览插件与用法示例【不上传图片】
2016/11/25 Javascript
AngularJS 使用ng-repeat报错 [ngRepeat:dupes]
2017/01/19 Javascript
解决Vue在Tomcat8下部署页面不加载的问题
2019/11/12 Javascript
基于element-ui封装可搜索的懒加载tree组件的实现
2020/05/22 Javascript
npm ci命令的基本使用方法
2020/09/20 Javascript
javascript实现多边形碰撞检测
2020/10/24 Javascript
Vue中computed和watch有哪些区别
2020/12/19 Vue.js
[01:22:10]Ti4 循环赛第二日 DK vs Empire
2014/07/11 DOTA
[01:06:19]DOTA2-DPC中国联赛定级赛 LBZS vs SAG BO3第二场 1月8日
2021/03/11 DOTA
python 七种邮件内容发送方法实例
2014/04/22 Python
Python实现备份文件实例
2014/09/16 Python
详解Python的Django框架中的通用视图
2015/05/04 Python
Django模板变量如何传递给外部js调用的方法小结
2017/07/24 Python
利用Python2下载单张图片与爬取网页图片实例代码
2017/12/25 Python
在Python中os.fork()产生子进程的例子
2019/08/08 Python
python IP地址转整数
2020/11/20 Python
python 用Matplotlib作图中有多个Y轴
2020/11/28 Python
详解css3 flex弹性盒自动铺满写法
2020/09/17 HTML / CSS
html5在移动端的屏幕适应问题示例探讨
2014/06/15 HTML / CSS
幼儿教师培训感言
2014/03/08 职场文书
运动员获奖感言
2014/08/15 职场文书
租房安全协议书
2014/08/20 职场文书
企业法人代表证明书
2014/09/27 职场文书
工程款申请报告
2015/05/15 职场文书
高中军训感想
2015/08/07 职场文书
教你用Python写一个植物大战僵尸小游戏
2021/04/25 Python
html,css,javascript是怎样变成页面的
2023/05/07 HTML / CSS