Python源码学习笔记 3 字符串对象

1.PyStringObject

typedef struct {
    PyObject_VAR_HEAD
    long ob_shash; //存储字符串hash值,初始为-1
    int ob_sstate; //表明该字符串是否经过intend处理
    char ob_sval[1];//实际存储字符串位置,默认为一个字符数组,但创建时会根据size大小扩展此空间
} PyStringObject;

PyString_Type:
类型对象

[stringobject.c]
PyTypeObject PyString_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "str",
    sizeof(PyStringObject),
    sizeof(char), //tp_itemsize指明变长对象保存的单位长度为一个char
    ……
    (reprfunc)string_repr,          /* tp_repr */
    &string_as_number,               /* tp_as_number */
    &string_as_sequence,            /* tp_as_sequence */
    &string_as_mapping,             /* tp_as_mapping */
    (hashfunc)string_hash,          /* tp_hash */
    0,                  /* tp_call */
    ……
    string_new,             /* tp_new */
    PyObject_Del,                       /* tp_free */
};

2.PyStringObject的创建

PyString_FromString:

 [stringobject.c]
PyObject* PyString_FromString(const char *str)
{
    register size_t size;
    register PyStringObject *op;

    size = strlen(str);  //计算size
    if (size > PY_SSIZE_T_MAX) {   //判断字符串长度是否超过范围
        return NULL;
    }

    if (size == 0 && (op = nullstring) != NULL) { //nullstring对象可能未创建
        return (PyObject *)op;
    }

    if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) { //判断单字符是否在缓冲区
        return (PyObject *)op;
    }

   /*创建字符串对象,并初始化*/
    op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
      //根据size大小调整空间
    PyObject_INIT_VAR(op, &PyString_Type, size);
    op->ob_shash = -1;
    op->ob_sstate = SSTATE_NOT_INTERNED;
    memcpy(op->ob_sval, str, size+1);
    ……
    return (PyObject *) op;
}

Python源码学习笔记 3 字符串对象_第1张图片

PyString_FromStringAndSize:
和FromString极为相似,只不过需要自行输入size值,则结尾可以不是NULL

 [stringobject.c]
PyObject* PyString_FromStringAndSize(const char *str, int size)
{
    register PyStringObject *op;
    //处理null string
    if(size == 0 && (op = nullstring) != NULL) {
        return (PyObject *)op;
    }
    //处理字符
    if(size == 1 && str != NULL && (op = characters[*str & UCHAR_MAX]) !=
       NULL)
    {
        return (PyObject *)op;
    }
    //创建新的PyStringObject对象,并初始化
    //Inline PyObject_NewVar
    op = (PyStringObject *)PyObject_MALLOC(sizeof(PyStringObject) + size);
    PyObject_INIT_VAR(op, &PyString_Type, size);
    op->ob_shash = -1;
    op->ob_sstate = SSTATE_NOT_INTERNED;
    if (str != NULL)
        memcpy(op->ob_sval, str, size);
    op->ob_sval[size] = '\0';
    ……
    return (PyObject *) op;
}

3.字符串对象的删除

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 */
            op->ob_refcnt = 3; //在intend字典中该对象引用了两次,所以需要提前调整引用为3
            if (PyDict_DelItem(interned, op) != 0)
                Py_FatalError(
                    "deletion of interned string failed");
            break;

        case SSTATE_INTERNED_IMMORTAL: 
        //此状态可通过 PyString_InternImmortal(PyObject **p)修改
            Py_FatalError("Immortal interned string died.");

        default:
            Py_FatalError("Inconsistent interned string state.");
    }
    op->ob_type->tp_free(op);
}

4.Interned实现

interned机制用以避免相同字符串进行多次创建,仅生成一个对象,节省内存同时使字符串对象做比较时仅比较对象地址是否相同即可,提升了比较效率。实现上通过内部的PyDictObject来存储该字符串对象,特别注意的是存入字典时,引用计数的细微调整及其缘由。

[stringobjec.c]
void PyString_InternInPlace(PyObject **p)
{
    register PyStringObject *s = (PyStringObject *)(*p);
    PyObject *t;
    if (!PyString_CheckExact(s)) //检查是否为PyStringObject
        return;
    if (PyString_CHECK_INTERNED(s)) 
        return;
    if (interned == NULL) { //若interned为NULL则初始化一个字典对象
        interned = PyDict_New();
    }
   /* 分析当前字符串对象是否在interned字典中(key),如果存在则增加字典中相同对象的引用,
   减少当前字符对象引用*/
    t = PyDict_GetItem(interned, (PyObject *)s);
    if (t) {
        Py_INCREF(t);
        Py_DECREF(*p);
        *p = t;
        return;
    }
    /*若不在interned字典中则增加至其中*/
    PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s);
    s->ob_refcnt -= 2; //因为加入到interned字典中时对该对象多引用了2次,需要减少
    PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL; //修改Interned状态
}

5.字符串缓冲池

对于单字符情况,python中维护一个PyStringObject * 的数组,在字符创建时会先判定是否在该数组中,如果不在则初始化到该数组中,以便日后使用。虽然Interned提供了相同字符的缓存机制,但每次仍需要针对该字符串进行malloc操作,而对单字符则可直接返回该对象地址,提升效率
缓冲池的声明:
UCHAR_MAX 该常量和平台相关

[stringobject.c]
static PyStringObject *characters[UCHAR_MAX + 1]; 

字符创建时使用缓冲池:

[stringobject.c]
PyObject* PyString_FromStringAndSize(const char *str, int size)
{
    register PyStringObject *op;
    ……
    if(size == 1 && str != NULL && (op = characters[*str & UCHAR_MAX]) != NULL)
    {
        return (PyObject *)op;
    }
    ……
}

缓冲池元素的初始化:
Python源码学习笔记 3 字符串对象_第2张图片

[stringobject.c]
PyObject* PyString_FromStringAndSize(const char *str, int size)
{
    ……
    else if (size == 1 && str != NULL) 
    {
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t); //进行interned
        op = (PyStringObject *)t;
        characters[*str & UCHAR_MAX] = op; //赋值进缓冲池
        Py_INCREF(op); //引用计数增加,避免该缓冲被删除
    }
    return (PyObject *) op;
}

6.字符串连接的性能问题

由于字符串对象是不可变对象,所以对字符串进行普通的连接操作(+)时,会迭代的生成很多字符串对象,当待连接字符串数目庞大时,将严重影响性能。解决方法是使用字符串对象的join操作,join操作仅需分配一次内存,将序列中的item一次按照分割方法拼凑成一个字符串。
string_concat函数

[stringobject.c]
static PyObject* string_concat(register PyStringObject *a, register
   PyObject *bb)
{
    register unsigned int size;
    register PyStringObject *op;
    #define b ((PyStringObject *)bb)
    ……
    //计算字符串连接后的长度size
    size = a->ob_size + b->ob_size;

/* Inline PyObject_NewVar */
    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中
    memcpy(op->ob_sval, a->ob_sval, (int) a->ob_size);
    memcpy(op->ob_sval + a->ob_size, b->ob_sval, (int) b->ob_size);
    op->ob_sval[size] = '\0';
    return (PyObject *) op;
#undef b
}

string_join函数


 [stringobject.c]
static PyObject* string_join(PyStringObject *self, PyObject *orig)
{
    char *sep = PyString_AS_STRING(self);
    //假设调用”abc”.join(list),那么self就是“abc”对应的PyStringObject对象
    //所以seplen中存储这“abc”的长度
    const int seplen = PyString_GET_SIZE(self);
    PyObject *res = NULL;
    char *p;
    int seqlen = 0;
    size_t sz = 0;
    int i;
    PyObject *seq, *item;
    ……//获得list中PyStringObject对象的个数,保存在seqlen中

    //遍历list中每一个字符串,累加获得连接list中所有字符串后的长度
    for (i = 0; i < seqlen; i++) 
    {
        //seq为Python中的list对象,这里获得其中第i个字符串
        item = PySequence_Fast_GET_ITEM(seq, i);
        sz += PyString_GET_SIZE(item);
        if (i != 0)
            sz += seplen;
    }
    //创建长度为sz的PyStringObject对象
    res = PyString_FromStringAndSize((char*)NULL, (int)sz);
    //将list中的字符串拷贝到新创建的PyStringObject对象中
    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);
        memcpy(p, PyString_AS_STRING(item), n);
        p += n;
        if (i < seqlen - 1) 
          {
            memcpy(p, sep, seplen);
            p += seplen;
        }
    }
    return res;
}

你可能感兴趣的:(Python,python,源码)