EVE Light on Life

Python源码学习(2) 字符串

字符串对象

定义

不定长对象,在创建的时候才知道数据长度。

typedef struct {
	PyObject_VAR_HEAD
	long ob_shash;  //  缓存对象hash值,很有用
	int ob_sstate;   //   标记是否经过intern机制处理
	char ob_sval[1];   // 指向所维护的字符串,实际长度由ob_size维护
}

//对应的类型对象:
PyTypeObject PyString_Type = {
    ...
    sizeof(PyStringObject),   // tp_basesize
    sizeof(char),             // tp_itemsize
    ...

变长对象由tp_itemsizeob_size共同决定该对象额外申请的内存大小。

创建对象

通过PyString_FromString(const char *str)函数,str指向原始的以’\0’结尾的字符串。

函数分四种情况处理原生字符串:

  1. 字符串长度超限
  2. 字符串长度为0.返回nullstring对象
  3. 字符串长度为1.返回缓存字符对象
  4. 创建新的PyStringObject对象。初始化:ob_shash=-1,ob_sstate=SSTATE_NOT_INTERNED

intern机制

被intern后的字符串,运行期间字符串值相同的对象只创建一次。通过函数PyString_InternInPlace进行intern操作。

对一个字符串对象intern过程:

  1. 首先字符串对象已被创建,再被intern。
  2. 检查该字符串是否被intern过。
  3. 检查字典interned(也是在这个函数里新建)里是否存在与字符串对象相同值的键。
  4. 如果有则将新建的字符串引用次数减1(变成0, 即销毁它),同时将指针指向interned中的字符串对象。
  5. 如果没有则将该字符串对象加到interned字典中。

字符串销毁

关于intern字符串有三种情况:

  • SSTATE_NOT_INTERNED: 没有intern的字符串
  • SSTATE_INTERNED_MORTAL: 已经intern的字符串
  • SSTATE_INTERNED_IMMORTAL: 永久intern的字符串,不会被销毁。
static void
string_dealloc(PyObject *op)
{
    switch (PyString_CHECK_INTERNED(op)) {
        case SSTATE_NOT_INTERNED:		
            break;

        case SSTATE_INTERNED_MORTAL:
            /* revive dead object temporarily for DelItem */
            Py_REFCNT(op) = 3;     //已经intern的字符串实际引用为3,PyDict_DelItem函数会删除里面的两个引用。
            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);
}

字符缓冲池

定义了一个字符缓冲池characters[UCHAR_MAX + 1]和一个nullstring:

static PyStringObject *characters[UCHAR_MAX + 1];
static PyStringObject *nullstring;

创建一个长度为1的字符串也就是字符过程:

  1. 如果characters[*str & UCHAR_MAX])不为NULL直接返回缓冲的字符串。
  2. 创建字符对象
  3. 将字符对象intern。
  4. 将字符对象缓存至字符缓存池characters [UCHAR_MAX]
if (size == 1 && str != NULL &&
		(op = characters[*str & UCHAR_MAX]) != NULL)
{

/*----------------------------------*/


} else if (size == 1 && str != NULL) {
		PyObject *t = (PyObject *)op;
		PyString_InternInPlace(&t);
		op = (PyStringObject *)t;
		characters[*str & UCHAR_MAX] = op;
		Py_INCREF(op);
}
return (PyObject *) op;

字符串连接

1.通过“+”操作符会进行调用string_contat函数进行字符串连接,不过每次连接都会创建出新的字符串。

static Pyobject* string_contat(register PyStringObject *a, register PyStringObject *bb ){
	register unsigned int size;
	register PyStringObject *op;

	/* 字符串类型检查 */
	if (!PyString_Check(bb)) {
	#ifdef Py_USING_UNICODE
			if (PyUnicode_Check(bb))
					return PyUnicode_Concat((PyObject *)a, bb);
	#endif
			if (PyByteArray_Check(bb))
					return PyByteArray_Concat((PyObject *)a, bb);
			PyErr_Format(PyExc_TypeError,
									 "cannot concatenate 'str' and '%.200s' objects",
									 Py_TYPE(bb)->tp_name);
			return NULL;
	}

	/* 开始进行连接 */
	#definde b ((PyStringObject *)bb)

	//计算字符串连接后的长度size
	size = a->ob_size + b->ob_size;

	//创建新的PyStringObject对象, 其维护的用于存储字符的内存长度为size
	op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
	PyObject_INIT_VAR(op, &PyString_Type, size);
	op->ob_shash = -1;
	op->ob_sstate = SSTATE_NOT_INTERNED;

	//将a和b中的字符拷贝到新创建的PyStringObject中

}

2.利用PyStringObject对象的join操作,会调用string_join函数进行连接。

static PyObject *
string_join(PyStringObject *self, PyObject *orig)
{
    char *sep = PyString_AS_STRING(self);
    const Py_ssize_t seplen = PyString_GET_SIZE(self);  //调用字符串的长度
    PyObject *res = NULL;
    char *p;
    Py_ssize_t seqlen = 0;  //此处是seqlen,list中的字符串个数
    size_t sz = 0;		//list中所有字符串长度之和
    Py_ssize_t i;
    PyObject *seq, *item;		//seq代表python中list对象,item即里面的每一个

		/* 获取list中PyStringObject对象的个数保存在seqlen中 */
    seq = PySequence_Fast(orig, "");
    if (seq == NULL) {
        return NULL;
    }

    seqlen = PySequence_Size(seq);
    if (seqlen == 0) {
        Py_DECREF(seq);
        return PyString_FromString("");
    }
    if (seqlen == 1) {
        item = PySequence_Fast_GET_ITEM(seq, 0);
        if (PyString_CheckExact(item) || PyUnicode_CheckExact(item)) {
            Py_INCREF(item);
            Py_DECREF(seq);
            return item;
        }
    }

    /* There are at least two things to join, or else we have a subclass
     * of the builtin types in the sequence.
     * Do a pre-pass to figure out the total amount of space we'll
     * need (sz), see whether any argument is absurd, and defer to
     * the Unicode join if appropriate.
     */
		 /* 遍历list中每一个字符串,累加获得连接list中所有字符串长度 */
    for (i = 0; i < seqlen; i++) {
        const size_t old_sz = sz;
        item = PySequence_Fast_GET_ITEM(seq, i);
        if (!PyString_Check(item)){
#ifdef Py_USING_UNICODE
            if (PyUnicode_Check(item)) {
                /* Defer to Unicode join.
                 * CAUTION:  There's no gurantee that the
                 * original sequence can be iterated over
                 * again, so we must pass seq here.
                 */
                PyObject *result;
                result = PyUnicode_Join((PyObject *)self, seq);
                Py_DECREF(seq);
                return result;
            }
#endif
            PyErr_Format(PyExc_TypeError,
                         "sequence item %zd: expected string,"
                         " %.80s found",
                         i, Py_TYPE(item)->tp_name);
            Py_DECREF(seq);
            return NULL;
        }
        sz += PyString_GET_SIZE(item);
        if (i != 0)
            sz += seplen;
        if (sz < old_sz || sz > PY_SSIZE_T_MAX) {
            PyErr_SetString(PyExc_OverflowError,
                "join() result is too long for a Python string");
            Py_DECREF(seq);
            return NULL;
        }
    }

    /* Allocate result space. */
    res = PyString_FromStringAndSize((char*)NULL, sz);
    if (res == NULL) {
        Py_DECREF(seq);
        return NULL;
    }

    /* Catenate everything. */
    p = PyString_AS_STRING(res);
    for (i = 0; i < seqlen; ++i) {
        size_t n;
        item = PySequence_Fast_GET_ITEM(seq, i);
        n = PyString_GET_SIZE(item);
        Py_MEMCPY(p, PyString_AS_STRING(item), n);
        p += n;
        if (i < seqlen - 1) {
            Py_MEMCPY(p, sep, seplen);
            p += seplen;
        }
    }

    Py_DECREF(seq);
    return res;
}