python中字符串实现原理_Python源码剖析 -Python中的字符串对象

1.前言

关于Python中的字符串对象,这里必须先引入一个概念:

Python 中的变长对象分为两类:变长可变对象 - 例如 List,创建后还能添加、删除元素

变长不可变对象 - 例如 String,Tuple, 创建后,不再支持添加、删除等操作

2. PyStringObject初识

PyStringObject 是对字符串对象的实现方式。首先它是一个可变长度的对象,这个可变是只指在创建字符串对象的时候,这个长度并不固定。但是一旦创建完毕后,这个长度就固定了,不能再发生变化。

举例来说:

test_str = "Hello World"

test_url = "https://www.xtuz.net"

显而易见,test_str 的长度和 test_url 的长度并不一样,这个原因就是在字符串对象创建时 PyStringObject 并不限定长度,然后创建完毕后,改对象内部维护的字符串对象就不在改变了。

我们从源码中也可以进行佐证:

typedef struct {

PyObject_VAR_HEAD

long ob_shash;

int ob_sstate;

char ob_sval[1];

/* Invariants:

* ob_sval contains space for 'ob_size+1' elements.

* ob_sval[ob_size] == 0.

* ob_shash is the hash of the string or -1 if not computed yet.

* ob_sstate != 0 iff the string object is in stringobject.c's

* 'interned' dictionary; in this case the two references

* from 'interned' to this object are *not counted* in ob_refcnt.

*/

} PyStringObject;

PyObject_VAR_HEAD 中的 ob_size ,记录变长对象的内存大小,ov_sval 作为字符指针指向一段内存,这段内存就是实际字符串。比例 test_str 的 ob_size 则为11。

ob_shash 则是该对象的哈希值,这在 dict 类型中是非常有用的,作为 key 值存在。

ob_sstate 则是表明该对象是否经过 intern 机制处理,简单来说就是即值同样的字符串对象仅仅会保存一份,放在一个字符串储蓄池中,是共用的,当然,肯定不能改变,这也决定了字符串必须是不可变对象。

3. PyStringObject创建

从代码上来看,可以有多种创建 PyStringObject 的方式:

PyAPI_FUNC(PyObject *) PyString_FromStringAndSize(const char *, Py_ssize_t);

PyAPI_FUNC(PyObject *) PyString_FromString(const char *);

PyAPI_FUNC(PyObject *) PyString_FromFormatV(const char*, va_list)

Py_GCC_ATTRIBUTE((format(printf, 1, 0)));

PyAPI_FUNC(PyObject *) PyString_FromFormat(const char*, ...)

Py_GCC_ATTRIBUTE((format(printf, 1, 2)));

其中,最常用的则是 PyString_FromString(const char *);

代码实现如下:

PyObject *

PyString_FromString(const char *str)

{

register size_t size;

register PyStringObject *op;

assert(str != NULL);

size = strlen(str);

if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {

PyErr_SetString(PyExc_OverflowError,

"string is too long for a Python string");

return NULL;

}

if (size == 0 && (op = nullstring) != NULL) {

#ifdef COUNT_ALLOCS

null_strings++;

#endif

Py_INCREF(op);

return (PyObject *)op;

}

if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {

#ifdef COUNT_ALLOCS

one_strings++;

#endif

Py_INCREF(op);

return (PyObject *)op;

}

/* Inline PyObject_NewVar */

op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size);

if (op == NULL)

return PyErr_NoMemory();

(void)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;

}

简单来说,主要是三个逻辑:判断字符串是否过长,过长,则返回 null 指针

判断是否是空串,空串,则将引用

分配内存,并将字符串复制到 op->ob_sval 中

在完成创建后,内存布局如上所示

4. 字符缓冲池

字符串的intern机制与此类似,其实就是会为长度为1的的字符创建对象池。

if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {

#ifdef COUNT_ALLOCS

one_strings++;

#endif

Py_INCREF(op);

return (PyObject *)op;

}

/* 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);

}

每当创建长度为1的字符串的时候,都会把它存到 characters 里面,这样之后创建长度为1的字符时,如果检测到已经在characters里面了,就直接返回这个缓冲的对象,不用进行malloc,这也就是该缓冲池的作用。

5. 字符串对象的intern机制

在 CPython 中字符串的实现原理使用了一种叫做 Intern(字符串驻留)的技术来提高字符串效率。

先来看一段代码:

a='www.xtuz.net'

b='www.xtuz.net'

print(id(a), id(b))

print(a is b)

可以看到如下输出结果

(4420449312, 4420449312)

True

a 和 b 虽然值是一样的,但确实是两个不同的字符串对象,假设程序中存在大量值相同的字符串,系统就不得不为每个字符串重复地分配内存空间,显然,对系统来说是一种无谓的资源浪费。为了解决这种问题,Python 引入了 intern 机制。

Intern 是 Python 中的一个内建函数,该函数的作用就是对字符串进行 intern 机制处理,处理后返回字符串对象。我们发现但凡是值相同的字符串经过 intern 机制处理之后,返回的都是同一个字符串对象,这种方式在处理大数据的时候无疑能节省更多的内存空间,系统无需为相同的字符串重复分配内存,对于值相同的字符串共用一个对象即可。

Intern 实现 机制的方式非常简单,就是通过维护一个字符串储蓄池,这个池子是一个字典结构,如果字符串已经存在于池子中了就不再去创建新的字符串,直接返回之前创建好的字符串对象,如果之前还没有加入到该池子中,则先构造一个字符串对象,并把这个对象加入到池子中去,方便下一次获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值