python推荐源码阅读_Python 源码阅读 - string

本周进展不大(去掉北上, 选择余地太小了), 下周开始投简历:(

这一章, 就一张图, 代码比较多

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;

说明

1. PyObject_VAR_HEAD

PyStringObject是变长对象, 比定长对象多了一个ob_size字段

2. ob_shash

存储字符串的hash值, 如果还没计算等于-1

当string_hash被调用, 计算结果会被保存到这个字段一份, 后续不再进行计算

3. ob_sstate

如果是interned, !=0, 否则=0

interned后面说

4. char ob_sval[1]

字符指针指向一段内存, char数组指针, 指向一个ob_size+1大小数组(c中字符串最后要多一个字符`\0`表字符串结束)

结构

构造方法

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

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

两个构造方法其实区别不大,

PyString_FromStringAndSize 参数可以为`NULL`, 无论是否为`NULL`, 都会分配`size+1`个字节空间.(不为NULL的话字符数组会进行拷贝)

PyString_FromString, 参数不能为`NULL`, 且必须是`\0`结束的字符数组, 会调用c 语言string.h模块的strlen()函数计算字符串长度, 分配空间, 并将整个字符数组拷贝到ob_sval指向的内存

我们关注PyString_FromString就行

创建过程 PyString_FromString

定义

//默认未初始化, 均为NULL

static PyStringObject *characters[UCHAR_MAX + 1];

static PyStringObject *nullstring;

PyObject *

PyString_FromString(const char *str)

{

register size_t size;

register PyStringObject *op;

assert(str != NULL);

// 计算参数字符数组长度

size = strlen(str);

// 超长, 报错(PY_SSIZE_T_MAX平台相关,32位2GB)

if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {

PyErr_SetString(PyExc_OverflowError,

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

return NULL;

}

// 长度=0, 且nullstring已定义, 返回nullstring

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

#ifdef COUNT_ALLOCS

null_strings++;

#endif

Py_INCREF(op);

return (PyObject *)op;

}

// 字符缓冲池逻辑

// 长度=1, 且characters[*str & UCHAR_MAX]字符已定义

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

// 初始化

PyObject_INIT_VAR(op, &PyString_Type, size);

op->ob_shash = -1; //未计算hash, -1

op->ob_sstate = SSTATE_NOT_INTERNED; //未intern, 0

// 将字符数组拷贝到PyStringObject

Py_MEMCPY(op->ob_sval, str, size+1);

// 在nullstring和字符缓冲池对应位置未初始化时, 会走到这个逻辑

/* share short strings */

if (size == 0) {

PyObject *t = (PyObject *)op;

// 走intern, 后面说

PyString_InternInPlace(&t);

op = (PyStringObject *)t;

// 初始化nullstring

nullstring = op;

Py_INCREF(op);

} else if (size == 1) {

PyObject *t = (PyObject *)op;

// 走intern, 后面说

PyString_InternInPlace(&t);

op = (PyStringObject *)t;

// 初始化字符缓冲池对应位置

characters[*str & UCHAR_MAX] = op;

Py_INCREF(op);

}

// 返回

return (PyObject *) op;

}

步骤简化

1. 计算长度

2. 长度0, 空字符串, 是返回已定义好的nullstring

3. 长度1, 字符, 返回字符缓冲池里面的

4. 都不是, 分配内存, 初始化

结论

长度0/长度1, 会用到intern机制

注意, intern机制对长度>1的字符串也适用

interned机制

interned

/* This dictionary holds all interned strings. Note that references to

strings in this dictionary are *not* counted in the string's ob_refcnt.

When the interned string reaches a refcnt of 0 the string deallocation

function will delete the reference from this dictionary.

Another way to look at this is that to say that the actual reference

count of a string is: s->ob_refcnt + (s->ob_sstate?2:0)

*/

static PyObject *interned; //指针, 指向PyDictObject

interned定义

void

PyString_InternInPlace(PyObject **p)

{

register PyStringObject *s = (PyStringObject *)(*p);

PyObject *t;

// 检查值使用在PyStringObject上, 派生类不适用

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. */

// 不是字符串类型, 返回

if (!PyString_CheckExact(s))

return;

// 本身已经intern了(标志位ob_sstate), 不重复进行, 返回

if (PyString_CHECK_INTERNED(s))

return;

// 未初始化字典, 初始化之

if (interned == NULL) {

// 注意这里

interned = PyDict_New();

if (interned == NULL) {

PyErr_Clear(); /* Don't leave an exception */

return;

}

}

// 在interned字典中已存在, 修改, 返回intern独享

t = PyDict_GetItem(interned, (PyObject *)s);

if (t) {

Py_INCREF(t);

Py_DECREF(*p);

*p = t;

return;

}

// 在interned字典中不存在, 放进去

if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {

PyErr_Clear();

return;

}

// 加入interned字典(key-value), 会导致refcnt+2, 去掉, 保证当外部没有引用时, refcnt=0, 可以进行回收处理, (不-2, refcnt永远>=2)

/* The two references in interned are not counted by refcnt.

The string deallocator will take care of this */

Py_REFCNT(s) -= 2;

// 修改字符串对象的ob_sstate标志位, SSTATE_INTERNED_MORTAL

PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;

}

使用的地方

// 构造方法

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

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

// SSTATE_INTERNED_MORTAL, 计数0会被回收

PyObject *

PyString_InternFromString(const char *cp)

{

PyObject *s = PyString_FromString(cp);

if (s == NULL)

return NULL;

PyString_InternInPlace(&s);

return s;

}

// SSTATE_INTERNED_IMMORTAL, 永远不会被销毁

void

PyString_InternImmortal(PyObject **p)

示例

>>> a = ''

>>> b = ''

>>> id(a) == id(b)

True

>>> a = 'x'

>>> b = 'x'

>>> id(a) == id(b)

True

>>> a = "abc"

>>> b = "abc"

>>> id(a) == id(b)

True

python源代码自己也大量使用

dict_str = PyString_InternFromString("__dict__")

lenstr = PyString_InternFromString("__len__")

s_true = PyString_InternFromString("true")

empty_array = PyString_InternFromString("[]")

好处

一旦字符串被intern, 会被python保存到字典中, 整个python运行期间, 系统中有且仅有一个对象. 下次相同字符串再进入, 不会重复创建对象.

字符缓冲池

定义

UCHAR_MAX 平台相关

static PyStringObject *characters[UCHAR_MAX + 1];

在上面PyString_FromString可以看到, 字符缓冲池在使用中初始化(存在直接返回, 不存在建一个, 放interned字典中, 初始化字符缓冲池对应位置)

PyObject *t = (PyObject *)op;

// 走intern, 后面说

PyString_InternInPlace(&t);

op = (PyStringObject *)t;

// 初始化字符缓冲池对应位置

characters[*str & UCHAR_MAX] = op;

字符串销毁过程

static void

string_dealloc(PyObject *op)

{

// 是否intern

switch (PyString_CHECK_INTERNED(op)) {

// 非, 跳出 -> 回收内存

case SSTATE_NOT_INTERNED:

break;

// 普通, 从interned字典中删除, 跳出 -> 回收内存

case SSTATE_INTERNED_MORTAL:

/* revive dead object temporarily for DelItem */

Py_REFCNT(op) = 3;

if (PyDict_DelItem(interned, op) != 0)

Py_FatalError(

"deletion of interned string failed");

break;

// 永不回收的对象, 报错

case SSTATE_INTERNED_IMMORTAL:

Py_FatalError("Immortal interned string died.");

default:

Py_FatalError("Inconsistent interned string state.");

}

// 回收内存

Py_TYPE(op)->tp_free(op);

}

性能相关

+与 join

'a' + 'b' + 'c'

or

''.join(['a', 'b', 'c'])

可以查看string_concat方法和string_join方法的源代码

string_concat, 一次加=分配一次内存空间, 拷贝两次. N次链接, 需要N-1次内存分配.

string_join, 计算序列所有元素字符串总长度, 用PyString_FromStringAndSize((char*)NULL, sz)分配内存空间, 然后逐一拷贝. 一次内存操作.

changelog

2014-08-08 first version

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值