您好,登錄后才能下訂單哦!
Python中字符串對象的實現原理是什么?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
在Python世界中將對象分為兩種:一種是定長對象,比如整數,整數對象定義的時候就能確定它所占用的內存空間大小,另一種是變長對象,在對象定義時并不知道是多少,比如:str,list, set, dict等。
>>> import sys >>> sys.getsizeof(1000) 28 >>> sys.getsizeof(2000) 28 >>> sys.getsizeof("python") 55 >>> sys.getsizeof("java") 53
如上,整數對象所占用的內存都是28字節,和具體的值沒關系,而同樣都是字符串對象,不同字符串對象所占用的內存是不一樣的,這就是變長對象,對于變長對象,在對象定義時是不知道對象所占用的內存空間是多少的。
字符串對象在Python內部用PyStringObject表示,PyStringObject和PyIntObject一樣都屬于不可變對象,對象一旦創建就不能改變其值。(注意:變長對象和不可變對象是兩個不同的概念)。PythonStringObject的定義:
[stringobject.h] typedef struct { PyObject_VAR_HEAD long ob_shash; int ob_sstate; char ob_sval[1]; } PyStringObject;
不難看出Python的字符串對象內部就是由一個字符數組維護的,在整數的實現原理一文中提到PyObject_HEAD,對于PyObject_VAR_HEAD就是在PyObject_HEAD基礎上多出一個ob_size屬性:
[object.h] #define PyObject_VAR_HEAD PyObject_HEAD int ob_size; /* Number of items in variable part */ typedef struct { PyObject_VAR_HEAD } PyVarObject;
ob_size保存了變長對象中元素的長度,比如PyStringObject對象"Python"的ob_size為6。
ob_sval是一個初始大小為1的字符數組,且ob_sval[0] = '\0',但實際上創建一個PyStringObject時ob_sval指向的是一段長為ob_size+1個字節的內存。
ob_shash是字符串對象的哈希值,初始值為-1,在第一次計算出字符串的哈希值后,會把該值緩存下來,賦值給ob_shash。
ob_sstate用于標記該字符串對象是否進過intern機制處理(后文會介紹)。
PYSTRINGOBJECT對象創建過程
[stringobject.c] PyObject * PyString_FromString(const char *str) { register size_t size; register PyStringObject *op; assert(str != NULL); size = strlen(str); // [1] 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) { #ifdef COUNT_ALLOCS null_strings++; #endif Py_INCREF(op); return (PyObject *)op; } // [3] if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) { #ifdef COUNT_ALLOCS one_strings++; #endif Py_INCREF(op); return (PyObject *)op; } // [4] /* 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; 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; }
如果字符串的長度超出了Python所能接受的最大長度(32位平臺是2G),則返回Null。
如果是空字符串,那么返回特殊的PyStringObject,即nullstring。
如果字符串的長度為1,那么返回特殊PyStringObject,即onestring。
其他情況下就是分配內存,初始化PyStringObject,把參數str的字符數組拷貝到PyStringObject中的ob_sval指向的內存空間。
字符串的INTERN機制
PyStringObject的ob_sstate屬性用于標記字符串對象是否經過intern機制處理,intern處理后的字符串,比如"Python",在解釋器運行過程中始終只有唯一的一個字符串"Python"對應的PyStringObject對象。
>>> a = "python" >>> b = "python" >>> a is b True
如上所示,創建a時,系統首先會創建一個新的PyStringObject對象出來,然后經過intern機制處理(PyString_InternInPlace),接著查找經過intern機制處理的PyStringObject對象,如果發現有該字符串對應的PyStringObject存在,則直接返回該對象,否則把剛剛創建的PyStringObject加入到intern機制中。由于a和b字符串字面值是一樣的,因此a和b都指向同一個PyStringObject("python")對象。那么intern內部又是一個什么樣的機制呢?
[stringobject.c] static PyObject *interned; void PyString_InternInPlace(PyObject **p) { register PyStringObject *s = (PyStringObject *)(*p); PyObject *t; 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. */ // [1] if (!PyString_CheckExact(s)) return; // [2] if (PyString_CHECK_INTERNED(s)) return; // [3] if (interned == NULL) { interned = PyDict_New(); if (interned == NULL) { PyErr_Clear(); /* Don't leave an exception */ return; } } t = PyDict_GetItem(interned, (PyObject *)s); if (t) { Py_INCREF(t); Py_DECREF(*p); *p = t; return; } if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) { PyErr_Clear(); return; } /* The two references in interned are not counted by refcnt. The string deallocator will take care of this */ Py_REFCNT(s) -= 2; PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL; }
1.先類型檢查,intern機制只處理字符串
2.如果該PyStringObject對象已經進行過intern機制處理,則直接返回
3.interned其實一個字典對象,當它為null時,初始化一個字典對象,否則,看該字典中是否存在一個key為(PyObject *)s的value,如果存在,那么就把該對象的引用計數加1,臨時創建的那個對象的引用計數減1。否則,把(PyObject *)s同時作為key和value添加到interned字典中,與此同時它的引用計數減2,這兩個引用計數減2是因為被interned字典所引用,但這兩個引用不作為垃圾回收的判斷依據,否則,字符串對象永遠都不會被垃圾回收器收集了。
上述代碼中,給b賦值為"python"后,系統中創建了幾個PyStringObject對象呢?答案是:2,在創建b的時候,一定會有一個臨時的PyStringObject作為字典的key在interned中查找是否存在一個PyStringObject對象的值為"python"。
字符串的緩沖池
字符串除了有intern機制緩存字符串之外,字符串還有一種專門的短字符串緩沖池characters。用于緩存字符串長度為1的PyStringObject對象。
static PyStringObject *characters[UCHAR_MAX + 1]; //UCHAR_MAX = 255
創建長度為1的字符串時流程:
... else if (size == 1) { PyObject *t = (PyObject *)op; PyString_InternInPlace(&t); op = (PyStringObject *)t; characters[*str & UCHAR_MAX] = op; Py_INCREF(op);
首先創建一個PyStringObject對象。
進行intern操作
將PyStringObject緩存到characters中
引用計數增1
總結:
1. 字符串用PyStringObject表示
2. 字符串屬于變長對象
3. 字符串屬于不可變對象
4. 字符串用intern機制提高python的效率
5. 字符串有專門的緩沖池存儲長度為1的字符串對象
Python是一種跨平臺的、具有解釋性、編譯性、互動性和面向對象的腳本語言,其最初的設計是用于編寫自動化腳本,隨著版本的不斷更新和新功能的添加,常用于用于開發獨立的項目和大型項目。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。