python3中采用的是unicode编码方式
typedef struct {
PyObject_HEAD
Py_ssize_t length; /* Number of code points in the string */
Py_hash_t hash; /* Hash value; -1 if not set */
struct {
unsigned int interned:2;
unsigned int kind:3;
unsigned int compact:1;
unsigned int ascii:1;
unsigned int ready:1;
unsigned int :24;
} state;
wchar_t *wstr;
} PyASCIIObject;
typedef struct {
PyASCIIObject _base;
Py_ssize_t utf8_length;
char *utf8;
Py_ssize_t wstr_length;
} PyCompactUnicodeObject;
typedef struct {
PyCompactUnicodeObject _base;
union {
void *any;
Py_UCS1 *latin1;
Py_UCS2 *ucs2;
Py_UCS4 *ucs4;
} data; /* Canonical, smallest-form Unicode buffer */
} PyUnicodeObject;
如何创建一个字符串对象,python提供了很多的API来创建一个字符串对象,这只是其中的一种
PyObject *PyUnicode_FromString(const char *u)
{
size_t size = strlen(u);
if (size > PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError, "input too long");
return NULL;
}
return PyUnicode_DecodeUTF8Stateful(u, (Py_ssize_t)size, NULL, NULL);
}
PY_SSIZE_T_MAX是跟平台有关的值,在WIN64系统下数值应该为
4 294 967 295换算一下也就是4GB,而WIN32系统下则为2GB,一般我们是碰不到这个临界值的。
字符串对象的intern机制
PyUnicode_InternInPlace(PyObject **p)
{
PyObject *s = *p;
PyObject *t;
#ifdef Py_DEBUG
assert(s != NULL);
assert(_PyUnicode_CHECK(s));
#else
if (s == NULL || !PyUnicode_Check(s))
return;
#endif
/* If it's a subclass, we don't really know what putting
it in the interned dict might do. */
if (!PyUnicode_CheckExact(s))
return;
if (PyUnicode_CHECK_INTERNED(s))
return;
if (interned == NULL) {
interned = PyDict_New();
if (interned == NULL) {
PyErr_Clear(); /* Don't leave an exception */
return;
}
}
Py_ALLOW_RECURSION
t = PyDict_SetDefault(interned, s, s);
Py_END_ALLOW_RECURSION
if (t == NULL) {
PyErr_Clear();
return;
}
if (t != s) {
Py_INCREF(t);
Py_SETREF(*p, t);
return;
}
/* The two references in interned are not counted by refcnt.
The deallocator will take care of this */
Py_REFCNT(s) -= 2;
_PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL;
}
这里会进行两处检查,一次类型检查和一次状态检查。
类型检查:检查传入的对象是否时一个PyUnicodeObject对象,interb机制指挥应用在这个对象上
状态检查:检查传入的PyUnicodeObject对象是否已经被intern机制处理过了。
继续看代码,intern实际上指向的是PyDict_New创建的一个对象,实际上是创建了一个PyDictObject对象。
这个机制实际上就是,对传入的一个PyUnicodeObject对象s,python会在intern机制创建的interned集合中检查是否有满足以下条件的对象t:t中维护的原生字符串与s相同,如果存在,那么指向a的PyObject指针将会指向t,而s的引用计数减1,如果不存在这样的一个对象,那么就将s存入interned集合中。
还有一点,代码中将s的引用计数减2,这是为什么呢,因为,在将s添加到interned集合中的时候,对于s的引用又多了两次,interned中的指针不能作为s的有效引用,所以就需要在处理过后减去2。在Py_SETREF(*p, t);中,python会将对象p的引用减一从而销毁这个对象,它只是作为一个中间变量,创建之后有可能立即销毁。
字符串缓冲池
PyObject *PyUnicode_DecodeUTF8Stateful(const char *s,
Py_ssize_t size,
const char *errors,
Py_ssize_t *consumed)
{
...
if (size == 1 && (unsigned char)s[0] < 128) {
if (consumed)
*consumed = 1;
return get_latin1_char((unsigned char)s[0]);
}
...
}
这里可以看到,当创建的字符串为一个字符时,python会直接从get_latin1_char中获取,而不会重新创建。
get_latin1_char(unsigned char ch)
{
PyObject *unicode = unicode_latin1[ch];
if (!unicode) {
unicode = PyUnicode_New(1, ch);
if (!unicode)
return NULL;
PyUnicode_1BYTE_DATA(unicode)[0] = ch;
assert(_PyUnicode_CheckConsistency(unicode, 1));
unicode_latin1[ch] = unicode;
}
Py_INCREF(unicode);
return unicode;
}
python会先创建一个字符对象,并对这个对象i纪念性intern操作,再放到字符串缓冲池中。
PyUnicodeObject的执行效率
在早些python版本中,python字符串对象的“+”操作一直被认为执行效率很低,由于PyUnicodeObject是一个不可变对象,所以python在执行这个操作的时候会重新申请一块内存来将字符串纪念性拼接,如果你只拼接两个字符串可能看不出来什么问题,只要你将很多个字符串同时拼接,这个毛病就会显露出来了,python不会一次性将所有的字符串拼接在一起,每次只能拼接两个字符串,所以这个操作就在不停的申请内存,拼接字符串。
PyObject *PyUnicode_Concat(PyObject *left, PyObject *right)
{
PyObject *result;
Py_UCS4 maxchar, maxchar2;
Py_ssize_t left_len, right_len, new_len;
if (ensure_unicode(left) < 0)
return NULL;
if (!PyUnicode_Check(right)) {
PyErr_Format(PyExc_TypeError,
"can only concatenate str (not \"%.200s\") to str",
right->ob_type->tp_name);
return NULL;
}
if (PyUnicode_READY(right) < 0)
return NULL;
/* Shortcuts */
if (left == unicode_empty)
return PyUnicode_FromObject(right);
if (right == unicode_empty)
return PyUnicode_FromObject(left);
left_len = PyUnicode_GET_LENGTH(left);
right_len = PyUnicode_GET_LENGTH(right);
if (left_len > PY_SSIZE_T_MAX - right_len) {
PyErr_SetString(PyExc_OverflowError,
"strings are too large to concat");
return NULL;
}
new_len = left_len + right_len;
maxchar = PyUnicode_MAX_CHAR_VALUE(left);
maxchar2 = PyUnicode_MAX_CHAR_VALUE(right);
maxchar = Py_MAX(maxchar, maxchar2);
/* Concat the two Unicode strings */
result = PyUnicode_New(new_len, maxchar);
if (result == NULL)
return NULL;
_PyUnicode_FastCopyCharacters(result, 0, left, 0, left_len);
_PyUnicode_FastCopyCharacters(result, left_len, right, 0, right_len);
assert(_PyUnicode_CheckConsistency(result, 1));
return result;
}
这就是使用“+”拼接字符串时执行的代码。在拼接很多个字符串的时候推荐使用join,因为join只申请一次内存,然后将所有字符串进行逐一拷贝。
PyObject *PyUnicode_Join(PyObject *separator, PyObject *seq)
{
PyObject *res;
PyObject *fseq;
Py_ssize_t seqlen;
PyObject **items;
fseq = PySequence_Fast(seq, "can only join an iterable");
if (fseq == NULL) {
return NULL;
}
items = PySequence_Fast_ITEMS(fseq);
seqlen = PySequence_Fast_GET_SIZE(fseq);
res = _PyUnicode_JoinArray(separator, items, seqlen);
Py_DECREF(fseq);
return res;
}
PyObject *_PyUnicode_JoinArray(PyObject *separator, PyObject *const *items, Py_ssize_t seqlen)
{
PyObject *res = NULL; /* the result */
PyObject *sep = NULL;
Py_ssize_t seplen;
...
sz = 0; // 字符串的总长度
for (i = 0; i < seqlen; i++) {
size_t add_sz;
item = items[i];
if (!PyUnicode_Check(item)) {
PyErr_Format(PyExc_TypeError,
"sequence item %zd: expected str instance,"
" %.80s found",
i, Py_TYPE(item)->tp_name);
goto onError;
}
if (PyUnicode_READY(item) == -1)
goto onError;
add_sz = PyUnicode_GET_LENGTH(item);
item_maxchar = PyUnicode_MAX_CHAR_VALUE(item);
maxchar = Py_MAX(maxchar, item_maxchar);
if (i != 0) {
add_sz += seplen;
}
if (add_sz > (size_t)(PY_SSIZE_T_MAX - sz)) {
PyErr_SetString(PyExc_OverflowError,
"join() result is too long for a Python string");
goto onError;
}
sz += add_sz;
if (use_memcpy && last_obj != NULL) {
if (PyUnicode_KIND(last_obj) != PyUnicode_KIND(item))
use_memcpy = 0;
}
last_obj = item;
}
res = PyUnicode_New(sz, maxchar); // 在这里进行内存申请
if (res == NULL)
goto onError;
for (i = 0, res_offset = 0; i < seqlen; ++i) {
Py_ssize_t itemlen;
item = items[i];
/* Copy item, and maybe the separator. */
if (i && seplen != 0) { // 内存搬运
_PyUnicode_FastCopyCharacters(res, res_offset, sep, 0, seplen);
res_offset += seplen;
}
itemlen = PyUnicode_GET_LENGTH(item);
if (itemlen != 0) {
_PyUnicode_FastCopyCharacters(res, res_offset, item, 0, itemlen);
res_offset += itemlen;
}
assert(res_offset == PyUnicode_GET_LENGTH(res));
Py_XDECREF(sep);
assert(_PyUnicode_CheckConsistency(res, 1));
return res;
onError:
Py_XDECREF(sep);
Py_XDECREF(res);
return NULL;
}
(这部分代码有点长,只贴了部分代码)