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常用知识梳理(必看篇)
Mar 23 Python
python语言中with as的用法使用详解
Feb 23 Python
python中退出多层循环的方法
Nov 27 Python
python使用xlsxwriter实现有向无环图到Excel的转换
Dec 12 Python
详解Python3注释知识点
Feb 19 Python
pyhanlp安装介绍和简单应用
Feb 22 Python
Python制作微信好友背景墙教程(附完整代码)
Jul 17 Python
Python2和3字符编码的区别知识点整理
Aug 08 Python
Flask框架 CSRF 保护实现方法详解
Oct 30 Python
关于tf.reverse_sequence()简述
Jan 20 Python
Pandas —— resample()重采样和asfreq()频度转换方式
Feb 26 Python
Python中glob库实现文件名的匹配
Jun 18 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
虹吸壶煮咖啡26个注意事项
2021/03/03 冲泡冲煮
10款实用的PHP开源工具
2015/10/23 PHP
解决PHP上传非标准格式的图片pjpeg失败的方法
2017/03/12 PHP
ThinkPHP框架下微信支付功能总结踩坑笔记
2019/04/10 PHP
event.X和event.clientX的区别分析
2011/10/06 Javascript
js 中将多个逗号替换为一个逗号的代码
2014/06/07 Javascript
Javascript核心读书有感之语言核心
2015/02/01 Javascript
jQuery对指定元素中指定字符串进行替换的方法
2015/03/17 Javascript
JavaScript创建一个object对象并操作对象属性的用法
2015/03/23 Javascript
javascript判断变量是否有值的方法
2015/04/20 Javascript
JavaScript如何动态创建table表格
2020/08/02 Javascript
jQuery+Ajax实现无刷新操作
2016/01/04 Javascript
jQuery表格插件datatables用法详解
2020/11/23 Javascript
Bootstrap显示与隐藏简单实现代码
2017/03/06 Javascript
详解vue的数据binding绑定原理
2017/04/12 Javascript
微信小程序开发图片拖拽实例详解
2017/05/05 Javascript
在JS中如何把毫秒转换成规定的日期时间格式实例
2017/05/11 Javascript
Vue实战之vue登录验证的实现代码
2017/10/31 Javascript
HTML5+JS+JQuery+ECharts实现异步加载问题
2017/12/16 jQuery
微信小程序性能优化之checkSession的使用
2019/03/06 Javascript
在vue-cli3.0 中使用预处理器 (Sass/Less/Stylus) 配置全局变量操作
2020/08/10 Javascript
JS面向对象实现飞机大战
2020/08/26 Javascript
python实现将汉字转换成汉语拼音的库
2015/05/05 Python
听歌识曲--用python实现一个音乐检索器的功能
2016/11/15 Python
python游戏地图最短路径求解
2019/01/16 Python
Python定时任务随机时间执行的实现方法
2019/08/14 Python
pycharm双击无响应(打不开问题解决办法)
2020/01/10 Python
详解Tensorflow不同版本要求与CUDA及CUDNN版本对应关系
2020/08/04 Python
python3 re返回形式总结
2020/11/20 Python
CSS3 animation ? steps 函数详解
2019/08/30 HTML / CSS
Foreo国际站:Foreo International
2018/10/29 全球购物
外企测试工程师面试题
2015/02/01 面试题
中专生的个人自我评价
2013/12/11 职场文书
质量保证书范本
2014/04/29 职场文书
2015年检验科工作总结
2015/04/27 职场文书
解决hive中导入text文件遇到的坑
2021/04/07 Python