[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; }
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 */ };
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; }
与PyString_FromString不同的是,PyString_FromStringAndSize对于传入的str是否以'\0'要求不严格,因为传入的size指明了需要拷贝的字符串的个数。
[python源码剖析]字符串对象(二):http://www.cnblogs.com/vipchenwei/articles/6945948.html