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中使用装饰器时需要注意的一些问题
May 11 Python
python基础教程之Filter使用方法
Jan 17 Python
Python用imghdr模块识别图片格式实例解析
Jan 11 Python
Python实现的矩阵转置与矩阵相乘运算示例
Mar 26 Python
pyqt5 comboBox获得下标、文本和事件选中函数的方法
Jun 14 Python
pytorch中的自定义反向传播,求导实例
Jan 06 Python
opencv python图像梯度实例详解
Feb 04 Python
将pytorch转成longtensor的简单方法
Feb 18 Python
翻转数列python实现,求前n项和,并能输出整个数列的案例
May 03 Python
python用什么编辑器进行项目开发
Jun 17 Python
Python Tricks 使用 pywinrm 远程控制 Windows 主机的方法
Jul 21 Python
如何编写python的daemon程序
Jan 07 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
生成ubuntu自动切换壁纸xml文件的php代码
2010/07/17 PHP
php去掉URL网址中带有PHPSESSID的配置方法
2014/07/08 PHP
laravel框架中间件 except 和 only 的用法示例
2019/07/12 PHP
laravel 实现关闭CSRF(全部关闭、部分关闭)
2019/10/21 PHP
extjs 学习笔记(一) 一些基础知识
2009/10/13 Javascript
JQuery插件开发示例代码
2013/11/06 Javascript
js写的评论分页(还不错)
2013/12/23 Javascript
jQuery标签编辑插件Tagit使用指南
2015/04/21 Javascript
JavaScript中用sort()方法对数组元素进行排序的操作
2015/06/09 Javascript
JavaScript简单判断复选框是否选中及取出值的方法
2015/08/13 Javascript
BootStrap初学者对弹出框和进度条的使用感觉
2016/06/27 Javascript
如何利用JSHint减少JavaScript的错误
2016/08/23 Javascript
js返回顶部实例分享
2016/12/21 Javascript
原生js实现倒计时--2018
2017/02/21 Javascript
bootstrap+jQuery 实现下拉菜单中复选框全选和全不选效果
2017/06/12 jQuery
详解React-Native全球化多语言切换工具库react-native-i18n
2017/11/03 Javascript
Angular2学习笔记之数据绑定的示例代码
2018/01/03 Javascript
JS实现生成由字母与数字组合的随机字符串功能详解
2018/05/25 Javascript
JS实现数组的增删改查操作示例
2018/08/29 Javascript
手把手教您实现react异步加载高阶组件
2020/04/07 Javascript
python发送伪造的arp请求
2014/01/09 Python
在Python中使用异步Socket编程性能测试
2014/06/25 Python
Python安装Imaging报错:The _imaging C module is not installed问题解决方法
2014/08/22 Python
python使用PIL缩放网络图片并保存的方法
2015/04/24 Python
使用Python编写基于DHT协议的BT资源爬虫
2016/03/19 Python
Python Requests 基础入门
2016/04/07 Python
django queryset相加和筛选教程
2020/05/18 Python
C语言中break与continue的区别
2012/07/12 面试题
保送生自荐信范文
2013/10/06 职场文书
办公室主任先进事迹
2014/01/18 职场文书
机电一体化专业毕业生自荐信
2014/06/19 职场文书
组工干部对照检查材料
2014/08/25 职场文书
2015年财务试用期工作总结
2014/12/24 职场文书
2015年清剿火患专项行动工作总结
2015/07/27 职场文书
Redis延迟队列和分布式延迟队列的简答实现
2021/05/13 Redis
python热力图实现的完整实例
2022/06/25 Python