PYTHON 源码阅读 - STRING,pythonstring,未经作者许可,禁止转载!
PYTHON 源码阅读 - STRING,pythonstring,未经作者许可,禁止转载!
本文作者: 编橙之家 - wklken 。未经作者许可,禁止转载!欢迎加入编橙之家 专栏作者。
这一章, 就一张图, 代码比较多
PyStringObject
源码位置 Include/stringobject.h |
Objects/stringobject.c
定义
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. PyObject_VAR_HEAD PyStringObject是变长对象, 比定长对象多了一个ob_size字段 2. ob_shash 存储字符串的hash值, 如果还没计算等于-1 当string_hash被调用, 计算结果会被保存到这个字段一份, 后续不再进行计算 3. ob_sstate 如果是interned, !=0, 否则=0 interned后面说 4. char ob_sval[1] 字符指针指向一段内存, char数组指针, 指向一个ob_size+1大小数组(c中字符串最后要多一个字符``表字符串结束)
结构
构造方法
PyAPI_FUNC(PyObject *) PyString_FromString(const char *); PyAPI_FUNC(PyObject *) PyString_FromStringAndSize(const char *, Py_ssize_t);
两个构造方法其实区别不大,
PyString_FromStringAndSize 参数可以为`NULL`, 无论是否为`NULL`, 都会分配`size+1`个字节空间.(不为NULL的话字符数组会进行拷贝) PyString_FromString, 参数不能为`NULL`, 且必须是``结束的字符数组, 会调用c 语言string.h模块的strlen()函数计算字符串长度, 分配空间, 并将整个字符数组拷贝到ob_sval指向的内存
我们关注PyString_FromString
就行
创建过程 PyString_FromString
定义
//默认未初始化, 均为NULL static PyStringObject *characters[UCHAR_MAX + 1]; static PyStringObject *nullstring; PyObject * PyString_FromString(const char *str) { register size_t size; register PyStringObject *op; assert(str != NULL); // 计算参数字符数组长度 size = strlen(str); // 超长, 报错(PY_SSIZE_T_MAX平台相关,32位2GB) if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) { PyErr_SetString(PyExc_OverflowError, "string is too long for a Python string"); return NULL; } // 长度=0, 且nullstring已定义, 返回nullstring if (size == 0 & (op = nullstring) != NULL) { #ifdef COUNT_ALLOCS null_strings++; #endif Py_INCREF(op); return (PyObject *)op; } // 字符缓冲池逻辑 // 长度=1, 且characters[*str & UCHAR_MAX]字符已定义 if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) { #ifdef COUNT_ALLOCS one_strings++; #endif Py_INCREF(op); return (PyObject *)op; } // 申请内存空间 /* 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; //未计算hash, -1 op->ob_sstate = SSTATE_NOT_INTERNED; //未intern, 0 // 将字符数组拷贝到PyStringObject Py_MEMCPY(op->ob_sval, str, size+1); // 在nullstring和字符缓冲池对应位置未初始化时, 会走到这个逻辑 /* share short strings */ if (size == 0) { PyObject *t = (PyObject *)op; // 走intern, 后面说 PyString_InternInPlace(&t); op = (PyStringObject *)t; // 初始化nullstring nullstring = op; Py_INCREF(op); } else if (size == 1) { PyObject *t = (PyObject *)op; // 走intern, 后面说 PyString_InternInPlace(&t); op = (PyStringObject *)t; // 初始化字符缓冲池对应位置 characters[*str & UCHAR_MAX] = op; Py_INCREF(op); } // 返回 return (PyObject *) op; }
步骤简化
1. 计算长度 2. 长度0, 空字符串, 是返回已定义好的nullstring 3. 长度1, 字符, 返回字符缓冲池里面的 4. 都不是, 分配内存, 初始化
结论
长度0/长度1, 会用到intern机制 注意, intern机制对长度>1的字符串也适用
interned机制
interned
/* This dictionary holds all interned strings. Note that references to strings in this dictionary are *not* counted in the string's ob_refcnt. When the interned string reaches a refcnt of 0 the string deallocation function will delete the reference from this dictionary. Another way to look at this is that to say that the actual reference count of a string is: s->ob_refcnt + (s->ob_sstate?2:0) */ static PyObject *interned; //指针, 指向PyDictObject
interned定义
void PyString_InternInPlace(PyObject **p) { register PyStringObject *s = (PyStringObject *)(*p); PyObject *t; // 检查值使用在PyStringObject上, 派生类不适用 if (s == NULL || !PyString_Check(s)) Py_FatalError("PyString_InternInPlace: strings only please!"); /* If it's a string subclass, we don't really know what putting it in the interned dict might do. */ // 不是字符串类型, 返回 if (!PyString_CheckExact(s)) return; // 本身已经intern了(标志位ob_sstate), 不重复进行, 返回 if (PyString_CHECK_INTERNED(s)) return; // 未初始化字典, 初始化之 if (interned == NULL) { // 注意这里 interned = PyDict_New(); if (interned == NULL) { PyErr_Clear(); /* Don't leave an exception */ return; } } // 在interned字典中已存在, 修改, 返回intern独享 t = PyDict_GetItem(interned, (PyObject *)s); if (t) { Py_INCREF(t); Py_DECREF(*p); *p = t; return; } // 在interned字典中不存在, 放进去 if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) 0) { PyErr_Clear(); return; } // 加入interned字典(key-value), 会导致refcnt+2, 去掉, 保证当外部没有引用时, refcnt=0, 可以进行回收处理, (不-2, refcnt永远>=2) /* The two references in interned are not counted by refcnt. The string deallocator will take care of this */ Py_REFCNT(s) -= 2; // 修改字符串对象的ob_sstate标志位, SSTATE_INTERNED_MORTAL PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL; }
使用的地方
// 构造方法 PyAPI_FUNC(PyObject *) PyString_FromString(const char *); PyAPI_FUNC(PyObject *) PyString_FromStringAndSize(const char *, Py_ssize_t); // SSTATE_INTERNED_MORTAL, 计数0会被回收 PyObject * PyString_InternFromString(const char *cp) { PyObject *s = PyString_FromString(cp); if (s == NULL) return NULL; PyString_InternInPlace(&s); return s; } // SSTATE_INTERNED_IMMORTAL, 永远不会被销毁 void PyString_InternImmortal(PyObject **p)
示例
>>> a = '' >>> b = '' >>> id(a) == id(b) True >>> a = 'x' >>> b = 'x' >>> id(a) == id(b) True >>> a = "abc" >>> b = "abc" >>> id(a) == id(b) True
python源代码自己也大量使用
dict_str = PyString_InternFromString("__dict__") lenstr = PyString_InternFromString("__len__") s_true = PyString_InternFromString("true") empty_array = PyString_InternFromString("[]")
好处
一旦字符串被intern, 会被python保存到字典中, 整个python运行期间, 系统中有且仅有一个对象. 下次相同字符串再进入, 不会重复创建对象.
字符缓冲池
定义
UCHAR_MAX 平台相关 static PyStringObject *characters[UCHAR_MAX + 1];
在上面PyString_FromString
可以看到, 字符缓冲池在使用中初始化(存在直接返回, 不存在建一个, 放interned字典中, 初始化字符缓冲池对应位置)
PyObject *t = (PyObject *)op; // 走intern, 后面说 PyString_InternInPlace(&t); op = (PyStringObject *)t; // 初始化字符缓冲池对应位置 characters[*str & UCHAR_MAX] = op;
字符串销毁过程
static void string_dealloc(PyObject *op) { // 是否intern switch (PyString_CHECK_INTERNED(op)) { // 非, 跳出 -> 回收内存 case SSTATE_NOT_INTERNED: break; // 普通, 从interned字典中删除, 跳出 -> 回收内存 case SSTATE_INTERNED_MORTAL: /* revive dead object temporarily for DelItem */ Py_REFCNT(op) = 3; if (PyDict_DelItem(interned, op) != 0) Py_FatalError( "deletion of interned string failed"); break; // 永不回收的对象, 报错 case SSTATE_INTERNED_IMMORTAL: Py_FatalError("Immortal interned string died."); default: Py_FatalError("Inconsistent interned string state."); } // 回收内存 Py_TYPE(op)->tp_free(op); }
性能相关
+
与 join
'a' + 'b' + 'c' or ''.join(['a', 'b', 'c'])
可以查看string_concat
方法和string_join
方法的源代码
string_concat, 一次加=分配一次内存空间, 拷贝两次. N次链接, 需要N-1次内存分配. string_join, 计算序列所有元素字符串总长度, 用PyString_FromStringAndSize((char*)NULL, sz)分配内存空间, 然后逐一拷贝. 一次内存操作.
changelog
2014-08-08 first version
打赏支持我写出更多好文章,谢谢!
打赏作者
打赏支持我写出更多好文章,谢谢!
任选一种支付方式
热门文章:
相关内容
- 暂无相关文章
评论关闭