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

在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;
}

先类型检查,intern机制只处理字符串
如果该PyStringObject对象已经进行过intern机制处理,则直接返回
interned其实一个字典对象,当它为null时,初始化一个字典对象,否则,看该字典中是否存在一个key为(PyObject *)s的value,如果存在,那么就把该对象的引用计数加1,临时创建的那个对象的引用计数减1。否则,把(PyObject *)s同时作为key和value添加到interned字典中,与此同时它的引用计数减2,这两个引用计数减2是因为被interned字典所引用,但这两个引用不作为垃圾回收的判断依据,否则,字符串对象永远都不会被垃圾回收器收集了。

上述代码中,给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

总结:

1. 字符串用PyStringObject表示

2. 字符串属于变长对象

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

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

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

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

(0)

相关推荐

  • Python字典对象实现原理详解

    字典类型是Python中最常用的数据类型之一,它是一个键值对的集合,字典通过键来索引,关联到相对的值,理论上它的查询复杂度是 O(1) : >>> d = {'a': 1, 'b': 2} >>> d['c'] = 3 >>> d {'a': 1, 'b': 2, 'c': 3} 在字符串的实现原理文章中,曾经出现过字典对象用于intern操作,那么字典的内部结构是怎样的呢?PyDictObject对象就是dict的内部实现. 哈希表 (HASH TA

  • Python检测一个对象是否为字符串类的方法

    目的 测试一个对象是否是字符串 方法 Python的字符串的基类是basestring,包括了str和unicode类型.一般可以采用以下方法: 复制代码 代码如下: def isAString(anobj): return isinstance(anobj,basestring) 不过以上方法对于UserString类的实例,无能无力. 复制代码 代码如下: In [30]: b=UserString.UserString('abc') In [31]: isAString(b) Out[31

  • Python日期时间对象转换为字符串的实例

    1.标准转换格式符号说明 %a 本地星期的短名称 如:Sun, Mon, ..., Sat (en_US); So, Mo, ..., Sa (de_DE) %A 本地星期全名称 如 :Sunday, Monday, ..., Saturday (en_US);Sonntag, Montag, ..., Samstag (de_DE) %w 星期的数字表示,0表示周日,6表示周六 如:0,1,2,,,6 %d 日的数字表示,并且使用0来填补(0-9),如:01, 02, ..., 31 %b 月

  • Python列表对象实现原理详解

    Python中的列表基于PyListObject实现,列表支持元素的插入.删除.更新操作,因此PyListObject是一个变长对象(列表的长度随着元素的增加和删除而变长和变短),同时它还是一个可变对象(列表中的元素根据列表的操作而发生变化,内存大小动态的变化),PyListObject的定义: typedef struct { # 列表对象引用计数 int ob_refcnt; # 列表类型对象 struct _typeobject *ob_type; # 列表元素的长度 int ob_siz

  • Python对象转JSON字符串的方法

    本文实例讲述了Python对象转JSON字符串的方法.分享给大家供大家参考,具体如下: import json class JSONObject(object): def __init__(self): self.name = 'Ahan' self.email = 'www@qq.com' self.age = 26 if __name__ == '__main__': o = JSONObject() print json.dumps(o, default=lambda o: o.__dic

  • Python判断两个对象相等的原理

    概述 大部分的python程序员平时编程的时候,很少关心两个对象为什么相等,因为教程和经验来说,他们就应该相等,比如1==1就应该返回True,可是当我们想要定义自己的对象或者修改默认的对象行为时,通常会因为不了解原理而导致各种奇奇怪怪的错误. 两个对象如何相等 两个对象如何才能相等要比我们想象的复杂很多,但核心的方法是重写 eq 方法,这个方法返回True,则表示两个对象相等,否则,就不相等.相反的,如果两个对象不相等,则重写 ne 方法. 默认情况下,如果你没有实现这个方法,则使用父类(ob

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

    在Python世界中将对象分为两种:一种是定长对象,比如整数,整数对象定义的时候就能确定它所占用的内存空间大小,另一种是变长对象,在对象定义时并不知道是多少,比如:str,list, set, dict等. >>> import sys >>> sys.getsizeof(1000) 28 >>> sys.getsizeof(2000) 28 >>> sys.getsizeof("python") 55 >&

  • Python整数对象实现原理详解

    整数对象在Python内部用PyIntObject结构体表示: typedef struct { PyObject_HEAD long ob_ival; } PyIntObject; PyObject_HEAD宏中定义的两个属性分别是: int ob_refcnt; struct _typeobject *ob_type; 这两个属性是所有Python对象固有的: ob_refcnt:对象的引用计数,与Python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制 ob_type:用于描述P

  • Python字典底层实现原理详解

    在Python中,字典是通过散列表或说哈希表实现的.字典也被称为关联数组,还称为哈希数组等.也就是说,字典也是一个数组,但数组的索引是键经过哈希函数处理后得到的散列值.哈希函数的目的是使键均匀地分布在数组中,并且可以在内存中以O(1)的时间复杂度进行寻址,从而实现快速查找和修改.哈希表中哈希函数的设计困难在于将数据均匀分布在哈希表中,从而尽量减少哈希碰撞和冲突.由于不同的键可能具有相同的哈希值,即可能出现冲突,高级的哈希函数能够使冲突数目最小化.Python中并不包含这样高级的哈希函数,几个重要

  • golang字符串本质与原理详解

    目录 一.字符串的本质 1.字符串的定义 2.字符串的长度 3.字符与符文 二.字符串的原理 1.字符串的解析 2.字符串的拼接 3.字符串的转换 总结 一.字符串的本质 1.字符串的定义 golang中的字符(character)串指的是所有8比特位字节字符串的集合,通常(非必须)是UTF-8 编码的文本. 字符串可以为空,但不能是nil. 字符串在编译时即确定了长度,值是不可变的. // go/src/builtin/builtin.go // string is the set of al

  • Python图像处理之边缘检测原理详解

    目录 原理 Sobel检测算子 Laplacian算子 算子比较 原理 边缘检测是图像处理和计算机视觉当中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点,图像的边缘检测可以大幅度的减少数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性,它们绝大多数可以分为两类:基于搜索和基于零穿越. 基于搜索:通过寻找图像一阶导数中max来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并在此方向找到局部梯度模的最大值,代表的算法是Sobel算子和Scharr算子.

  • python super用法及原理详解

    这篇文章主要介绍了python super用法及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 概念 super作为python的内建函数.主要作用如下: 允许我们避免使用基类 跟随多重继承来使用 实例 在单个继承的场景下,一般使用super来调用基类来实现: 下面是一个例子: class Mammal(object): def __init__(self, mammalName): print(mammalName, 'is a wa

  • Python模块future用法原理详解

    这篇文章主要介绍了Python模块future用法原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 计算机的知识太多了,很多东西就是一个使用过程中详细积累的过程.最近遇到了一个很久关于future的问题,踩了坑,这里就做个笔记,免得后续再犯类似错误. future的作用:把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性.说的通俗一点,就是你不用更新python的版本,直接加这个模块,就可以使用python

  • Python日志syslog使用原理详解

    这篇文章主要介绍了Python日志syslog使用原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 syslog的官方说明在: https://docs.python.org/2/library/syslog.html#module-syslog 该模块的主要方式为: #!/usr/bin/python # -*- coding: utf-8 -*- import syslog syslog.openlog([ident[, logopt

随机推荐