
1. 前言
我們已經在 【Python中的整數物件】 章節中對定長物件進行了詳細的講解,接下來我們將介紹變長物件,而字串型別,則是這類物件的典型代表,
這里必須先引入一個概念:
Python 中的變長物件分為兩類:
- 變長可變物件 - 例如
List,創建后還能添加、洗掉元素 - 變長不可變物件 - 例如
String,Tuple, 創建后,不再支持添加、洗掉等操作
2. PyStringObject初識
PyStringObject 是對字串物件的實作方式,首先它是一個可變長度的物件,這個可變是只指在創建字串物件的時候,這個長度并不固定,但是一旦創建完畢后,這個長度就固定了,不能再發生變化,
舉例來說:
test_str = "Hello World"
test_url = "https://www.xtuz.net"
顯而易見,test_str 的長度和 test_url 的長度并不一樣,這個原因就是在字串物件創建時 PyStringObject 并不限定長度,然后創建完畢后,改物件內部維護的字串物件就不在改變了,
我們從原始碼中也可以進行佐證:
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;
PyObject_VAR_HEAD 中的 ob_size (詳見Python原始碼剖析 - 物件初探),記錄變長物件的記憶體大小,ov_sval 作為字符指標指向一段記憶體,這段記憶體就是實際字串,比例 test_str 的 ob_size 則為11,
ob_shash 則是該物件的哈希值,這在 dict 型別中是非常有用的,作為 key 值存在,
ob_sstate 則是表明該物件是否經過 intern 機制處理,簡單來說就是即值同樣的字串物件僅僅會保存一份,放在一個字串儲蓄池中,是共用的,當然,肯定不能改變,這也決定了字串必須是不可變物件,
3. PyStringObject創建
從代碼上來看,可以有多種創建 PyStringObject 的方式:
PyAPI_FUNC(PyObject *) PyString_FromStringAndSize(const char *, Py_ssize_t);
PyAPI_FUNC(PyObject *) PyString_FromString(const char *);
PyAPI_FUNC(PyObject *) PyString_FromFormatV(const char*, va_list)
Py_GCC_ATTRIBUTE((format(printf, 1, 0)));
PyAPI_FUNC(PyObject *) PyString_FromFormat(const char*, ...)
Py_GCC_ATTRIBUTE((format(printf, 1, 2)));
其中,最常用的則是 PyString_FromString(const char *);
代碼實作如下:
PyObject *
PyString_FromString(const char *str)
{
register size_t size;
register PyStringObject *op;
assert(str != NULL);
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;
}
if (size == 0 && (op = nullstring) != NULL) {
#ifdef COUNT_ALLOCS
null_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
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();
(void)PyObject_INIT_VAR(op, &PyString_Type, size);
op->ob_shash = -1;
op->ob_sstate = SSTATE_NOT_INTERNED;
Py_MEMCPY(op->ob_sval, str, size+1);
/* 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) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
}
return (PyObject *) op;
}
簡單來說,主要是三個邏輯:
- 判斷字串是否過長,過長,則回傳 null 指標
- 判斷是否是空串,空串,則則將參考
- 分配記憶體,并將字串復制到 op->ob_sval 中

在完成創建后,記憶體布局如上所示
4. 字符緩沖池
我們已經在【Python中的整數物件】 中闡述了 Python 對小整數的優化處理,而字串的intern機制與此類似,其實就是會為長度為1的的字符創建物件池,
if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {
#ifdef COUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
/* 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) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
}
每當創建長度為1的字串的時候,都會把它存到 characters 里面,這樣之后創建長度為1的字符時,如果檢測到已經在characters里面了,就直接回傳這個緩沖的物件,不用進行malloc,這也就是該緩沖池的作用,
5. 字串物件的intern機制
在 CPython 中字串的實作原理使用了一種叫做 Intern(字串駐留)的技術來提高字串效率,
先來看一段代碼:
a='www.xtuz.net'
b='www.xtuz.net'
print(id(a), id(b))
print(a is b)
可以看到如下輸出結果
(4420449312, 4420449312)
True
a 和 b 雖然值是一樣的,但確實是兩個不同的字串物件,假設程式中存在大量值相同的字串,系統就不得不為每個字串重復地分配記憶體空間,顯然,對系統來說是一種無謂的資源浪費,為了解決這種問題,Python 引入了 intern 機制,
Intern 是 Python 中的一個內建函式,該函式的作用就是對字串進行 intern 機制處理,處理后回傳字串物件,我們發現但凡是值相同的字串經過 intern 機制處理之后,回傳的都是同一個字串物件,這種方式在處理大資料的時候無疑能節省更多的記憶體空間,系統無需為相同的字串重復分配記憶體,對于值相同的字串共用一個物件即可,
Intern 實作 機制的方式非常簡單,就是通過維護一個字串儲蓄池,這個池子是一個字典結構,如果字串已經存在于池子中了就不再去創建新的字串,直接回傳之前創建好的字串物件,如果之前還沒有加入到該池子中,則先構造一個字串物件,并把這個物件加入到池子中去,方便下一次獲取,
6. 更多內容
原文來自兔子先生網站:https://www.xtuz.net/detail-139.html
查看原文 >>> Python原始碼剖析 - Python中的字串物件
如果你對Python語言感興趣,可以關注我,或者關注我的微信公眾號:xtuz666
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/162696.html
標籤:Python
上一篇:機器學習第5章支持向量機
下一篇:python is和==的區別
