CPython-内置string类型

2021-06-03 11:01

阅读:656

typedef struct {
    PyObject_VAR_HEAD
    long ob_shash;        // 字符串hash值(-1表示还未计算)
    int ob_sstate;        // interned状态(0表示未被intern)
    char ob_sval[1];      // 字符数组(ob_size+1,包含结束符)
} PyStringObject;

  其中PyObject_VAR_HEAD是可变对象的基础数据部分,其中有ob_size表示可变对象的长度;

    ob_sval是字符数组,用来存在字符串,以‘\0‘结尾,所以它的长度是ob_size+1;

    ob_shash是字符串的hash值,因为字符串经常会被作为字典(dict)的键(key),不管是脚本中还是Python虚拟机中,使用一个字段来保存hash避免每次都需要重新计算(注意字符串对象是不可变对象);

    ob_sstate用来保存intern状态,后面会讲到。

[Python]
>>> s1 = ‘‘
>>> sys.getsizeof(s1)
29                       # 基础大小(PyStringObject_SIZE)
>>> s2 = abc
>>> sys.getsizeof(s2)
32

 

  PyString_Type

PyTypeObject PyString_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "str",
    PyStringObject_SIZE,
    sizeof(char),
    string_dealloc,                          /* tp_dealloc */
    (printfunc)string_print,                 /* tp_print */
    (hashfunc)string_hash,                   /* tp_hash */
    string_methods,                          /* tp_methods */
    ...
};

  PyObjectType中有计算字符串hash值的string_hash函数,有兴趣的可以查看string_hash函数的定义;

    以及string对象特有的方法string_methods,主要包括:join、split、rsplit、lower、upper、find、index、replace等。

  对于Python对象,通常需要关注对象创建、对象销毁、对象操作几部分。

 

  string对象创建

    创建string对象需要关注string对象内存分配、字符数组分配和赋值几个部分。可以直接看string对象创建函数:

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)空字符串nullstring
    if (size == 0 && (op = nullstring) != NULL) {
        Py_INCREF(op);
        return (PyObject *)op;
    }
    // (3)单字符串尝试从characters中获取
    if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
        Py_INCREF(op);
        return (PyObject *)op;
    }

    // (4)分配PyStringObject对象内存
    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;
    // (5)将字符串复制到对象内部的字符数组中
    Py_MEMCPY(op->ob_sval, str, size+1);

    if (size == 0) {
        // (6)长度为0的字符串进行intern操作
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t);
        op = (PyStringObject *)t;
        nullstring = op;
        Py_INCREF(op);
    } else if (size == 1) {
        // (7)长度为1的字符串进行intern操作
        PyObject *t = (PyObject *)op;
        PyString_InternInPlace(&t);
        op = (PyStringObject *)t;
        characters[*str & UCHAR_MAX] = op;
        Py_INCREF(op);
    }
    return (PyObject *) op;
}

  (1)保证字符串不超长。PY_SSIZE_T_MAX在不同设备和系统上值不同,在我的测试设备上(Windows 10)值是2147483647B(约2GB),应该几乎不可能会超值这个最大限制。

  (2)空字符串nullstring。所有的空字符串会共享nullstring静态对象(会增加引用计数)。

  (3)单字符串尝试从characters中获取(会增加引用计数)。characters是单字符串缓冲机制,是256个单字符串对象数组(UCHAR_MAX + 1),对应于ascii字符集。

    static PyStringObject *characters[UCHAR_MAX + 1];

  (4)分配PyStringObject对象内存。为string对象分配内存,大小为基础大小(PyStringObject_SIZE) + 字符串长度(size)。

  (5)将字符串复制到对象内部的字符数组中。

  (6)长度为0的字符串进行intern操作,首次创建nullstring对象,会进行intern操作。

  (7)长度为1的字符串进行intern操作,首次创建该单字符串对象,会进行intern操作。

  intern操作是实现string对象驻留机制,用于对string对象的共享。

 

  intern机制

    字符串对象驻留机制,即共享机制。通过驻留机制能够避免相同字符串对象的重复创建,仅通过增加引用计数来取用已存在的相同字符串对象,这一切是通过叫interned字典来实现。

    先通过Python脚本来观察intern机制的效果,然后再来看intern机制源代码。

 

 

  string对象销毁

    TODO

 


评论


亲,登录后才可以留言!