[python源码剖析]字符串对象(一)

本文主要阐述三个问题:

  1.PyStringObject(字符串对象)

  2.PyString_Type(字符串对象的类型)

  3.创建字符串对象

一、PyStringObject(字符串对象)

[stringobject.h]
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 #define PyObject_VAR_HEAD               \
2             PyObject_HEAD                       \
3             Py_ssize_t ob_size;

  PyStringObject是一个拥有可变长度内存的对象。比如:“hi”和"python"是两个不同的PyStringObject对象,其内部所需的保存字符串内容的内存空间显然是不一样的。

同时PyStringObject对象又是一个不变对象。为什么这样说呢?当创建一个PyStringObject对象之后,该对象内部维护的字符串就不能改变了。也就是在创建字符串对象时,其内存是不定的,在这个层面上,它是一个可变对象;但是一旦字符串创建后,其长度就不能改变了,在这个层面,它又是不变对象。

  PyObject_VAR_HEAD,其中有一个ob_size变量保存对象维护的可变长度内存的大小。

  

  char ob_sval[1];//作为一个字符指针指向一段内存,这段内存保存着字符串对象维护的实际字符串。

  内存的实际长度(字节),由ob_size维护,这是python所有变长对象的实现机制。比如PyStringObject对象“python”,ob_size值就是6。ob_sval指向一段长度为ob_size+1个字节的内存,因为在C语言中,字符串结尾的标志位'\0',而存在這样一种可能,字符串 中间就有'\0',因此,在pytho中,判断字符结束的条件是ob_sval[ob_size] == '\0'。

  ob_shash作用是缓存该对象的hash值,这样就避免了每一次重新计算该字符串对象的hash值。如果一个对象还没没有计算过hash值,那么初始ob_shash=-1。

  hash值采用以下算法: 

static long string_hash(PyStringObject *a){
                register Py_ssize_t len;
                register unsigned char *p;
                register long x;
                if (a->ob_shash != -1)
                    return a->ob_shash;
                len = Py_SIZE(a);
                if (len == 0) {
                    a->ob_shash = 0;
                    return 0;
                }
                p = (unsigned char *) a->ob_sval;
                x = _Py_HashSecret.prefix;
                x ^= *p << 7;
                while (--len >= 0)
                    x = (1000003*x) ^ *p++;
                x ^= Py_SIZE(a);
                x ^= _Py_HashSecret.suffix;
                if (x == -1)
                    x = -2;
                a->ob_shash = x;
                return x;
            }
View Code

  ob_sstate标记了该对象是否已经过intern机制的处理。上述的预存hash值和intern机制将python虚拟机的执行效率提升了20%

二、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 */
            0,                                          /* tp_getattr */
            0,                                          /* tp_setattr */
            0,                                          /* tp_compare */
            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_str,                                 /* tp_str */
            PyObject_GenericGetAttr,                    /* tp_getattro */
            0,                                          /* tp_setattro */
            &string_as_buffer,                          /* tp_as_buffer */
            Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
                Py_TPFLAGS_BASETYPE | Py_TPFLAGS_STRING_SUBCLASS |
                Py_TPFLAGS_HAVE_NEWBUFFER,              /* tp_flags */
            string_doc,                                 /* tp_doc */
            0,                                          /* tp_traverse */
            0,                                          /* tp_clear */
            (richcmpfunc)string_richcompare,            /* tp_richcompare */
            0,                                          /* tp_weaklistoffset */
            0,                                          /* tp_iter */
            0,                                          /* tp_iternext */
            string_methods,                             /* tp_methods */
            0,                                          /* tp_members */
            0,                                          /* tp_getset */
            &PyBaseString_Type,                         /* tp_base */
            0,                                          /* tp_dict */
            0,                                          /* tp_descr_get */
            0,                                          /* tp_descr_set */
            0,                                          /* tp_dictoffset */
            0,                                          /* tp_init */
            0,                                          /* tp_alloc */
            string_new,                                 /* tp_new */
            PyObject_Del,                               /* tp_free */
        };
View Code

  tp_itemsize被设置为sizeof(char),即一个字节。对于python中的任何一种变长对象,tp_itemsize这个必须设置的,tp_itemsize指明了由变长对象保存的元素(item)的单位长度,即一个元素在内存中的长度。这个tp_itemsize和ob_size共同决定了应该额外申请的内存是多少。至于其他的表示什么,这里暂时先不描述。

三、PyStringObject对象的创建

  PyStringObject对象创建有两种方式:第一种:从C中原生的字符串创建PyStringObject对象;第二种:FromStringAndSize.下面,我们分别来看看两种创建字符的异同。

  1.从C中原生的字符串创建PyStringObject对象

PyObject *PyString_FromString(const char *str){
            register size_t size;
            register PyStringObject *op;
            //[1]判断字符长度
            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;
            }
            //[2]处理空串
            if (size == 0 && (op = nullstring) != NULL) {
                return (PyObject *)op;
            }
            //[3]处理字符
            if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
                return (PyObject *)op;
            }
            //[4]创建新的PyStringObject对象,并初始化
            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
            op->ob_sstate = SSTATE_NOT_INTERNED;//设置intern机制标志位
            Py_MEMCPY(op->ob_sval, str, size+1);//拷贝字符
            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;
        }

  从传入的参数来看,该字符串必须以'\0'作为结束标志。我们来看看创建的流程:

    a.首先在[1]出检查该字符数组的长度,如果字符数组长度大于PY_SSIZE_T_MAX - PyStringObject_SIZE。那么python将不会创建对应的PyStringObject对象。

PY_SSIZE_T_MAX是一个与平台相关的值,在win32平台下,该值为2147483647,就是2GB。

    b.在[2]检查传入字符串是否为空串。对于空串,python并不是每一次都会创建PyStringObject。python运行时有一个PyStringObject对象指针,由于空串指针被初始化为NULL,所以python会为这个空字符建立一个PyStringObject对象,将这个PyStringObject对象通过intern机制共享,然后将nullstring指向这被共享的对象。如果在以后python检查到需要为一个空串创建PyStringObject对象,这时nullstring已经存在,直接返回nullstring的引用。

    c.如果不是空串,接下来就是申请内存了。从[4]可以看出除了申请PyStringObject对象内存外,还有为字符数组内的元素申请额外内存,然后将hash缓存设为-1,将intern标志设为SSTATE_NOT_INTERNED.最后将参数str指向的字符数组内的字符拷贝到PyStringObject所维护的空间中,通过也把末尾的'\0'字符也拷贝了。

#define SSTATE_NOT_INTERNED 0        //不使用intern机制
#define SSTATE_INTERNED_MORTAL 1     //永久保存
#define SSTATE_INTERNED_IMMORTAL 2  //销毁标志

   例如:“python”在内存中的模型如下:

   

  2.PyString_FromStringAndSize

PyObject *PyString_FromStringAndSize(const char *str, Py_ssize_t size){
            register PyStringObject *op;
            if (size < 0) {
                PyErr_SetString(PyExc_SystemError,
                    "Negative size passed to PyString_FromStringAndSize");
                return NULL;
            }
            //处理空串
            if (size == 0 && (op = nullstring) != NULL) {
            #ifdef COUNT_ALLOCS
                null_strings++;
            #endif
                Py_INCREF(op);
                return (PyObject *)op;
            }
            //处理字符
            if (size == 1 && str != NULL &&
                (op = characters[*str & UCHAR_MAX]) != NULL)
            {
            #ifdef COUNT_ALLOCS
                one_strings++;
            #endif
                Py_INCREF(op);
                return (PyObject *)op;
            }

            if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {
                PyErr_SetString(PyExc_OverflowError, "string is too large");
                return NULL;
            }
            //创建新的PyStringObject对象,并初始化
            /* 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;
            op->ob_sstate = SSTATE_NOT_INTERNED;
            if (str != NULL)
                Py_MEMCPY(op->ob_sval, str, size);
            op->ob_sval[size] = '\0';
            /* 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 && str != NULL) {
                PyObject *t = (PyObject *)op;
                PyString_InternInPlace(&t);
                op = (PyStringObject *)t;
                characters[*str & UCHAR_MAX] = op;
                Py_INCREF(op);
            }
            return (PyObject *) op;
        }
View Code

  与PyString_FromString不同的是,PyString_FromStringAndSize对于传入的str是否以'\0'要求不严格,因为传入的size指明了需要拷贝的字符串的个数。

 

 

  

[python源码剖析]字符串对象(二):http://www.cnblogs.com/vipchenwei/articles/6945948.html

 

  

  

posted @ 2017-06-05 08:20  看雪。  阅读(345)  评论(0编辑  收藏  举报