欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python字符串對象實(shí)現(xiàn)原理詳解

 更新時(shí)間:2019年07月01日 10:28:27   作者:FOOFISH-PYTHON之禪  
這篇文章主要介紹了Python字符串對象實(shí)現(xiàn)原理詳解,在Python世界中將對象分為兩種:一種是定長對象,比如整數(shù),整數(shù)對象定義的時(shí)候就能確定它所占用的內(nèi)存空間大小,另一種是變長對象,在對象定義時(shí)并不知道是多少,需要的朋友可以參考下

在Python世界中將對象分為兩種:一種是定長對象,比如整數(shù),整數(shù)對象定義的時(shí)候就能確定它所占用的內(nèi)存空間大小,另一種是變長對象,在對象定義時(shí)并不知道是多少,比如:str,list, set, dict等。

>>> import sys
>>> sys.getsizeof(1000)
28
>>> sys.getsizeof(2000)
28
>>> sys.getsizeof("python")
55
>>> sys.getsizeof("java")
53

如上,整數(shù)對象所占用的內(nèi)存都是28字節(jié),和具體的值沒關(guān)系,而同樣都是字符串對象,不同字符串對象所占用的內(nèi)存是不一樣的,這就是變長對象,對于變長對象,在對象定義時(shí)是不知道對象所占用的內(nèi)存空間是多少的。

字符串對象在Python內(nèi)部用PyStringObject表示,PyStringObject和PyIntObject一樣都屬于不可變對象,對象一旦創(chuàng)建就不能改變其值。(注意:變長對象和不可變對象是兩個(gè)不同的概念)。PythonStringObject的定義:

[stringobject.h]
typedef struct {
PyObject_VAR_HEAD
long ob_shash;
int ob_sstate;
char ob_sval[1];
} PyStringObject;

不難看出Python的字符串對象內(nèi)部就是由一個(gè)字符數(shù)組維護(hù)的,在整數(shù)的實(shí)現(xiàn)原理一文中提到PyObject_HEAD,對于PyObject_VAR_HEAD就是在PyObject_HEAD基礎(chǔ)上多出一個(gè)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是一個(gè)初始大小為1的字符數(shù)組,且ob_sval[0] = '\0',但實(shí)際上創(chuàng)建一個(gè)PyStringObject時(shí)ob_sval指向的是一段長為ob_size+1個(gè)字節(jié)的內(nèi)存。
  • ob_shash是字符串對象的哈希值,初始值為-1,在第一次計(jì)算出字符串的哈希值后,會把該值緩存下來,賦值給ob_shash。
  • ob_sstate用于標(biāo)記該字符串對象是否進(jìn)過intern機(jī)制處理(后文會介紹)。

PYSTRINGOBJECT對象創(chuàng)建過程

[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。
  • 其他情況下就是分配內(nèi)存,初始化PyStringObject,把參數(shù)str的字符數(shù)組拷貝到PyStringObject中的ob_sval指向的內(nèi)存空間。

字符串的INTERN機(jī)制

PyStringObject的ob_sstate屬性用于標(biāo)記字符串對象是否經(jīng)過intern機(jī)制處理,intern處理后的字符串,比如"Python",在解釋器運(yùn)行過程中始終只有唯一的一個(gè)字符串"Python"對應(yīng)的PyStringObject對象。

>>> a = "python"
>>> b = "python"
>>> a is b
True

如上所示,創(chuàng)建a時(shí),系統(tǒng)首先會創(chuàng)建一個(gè)新的PyStringObject對象出來,然后經(jīng)過intern機(jī)制處理(PyString_InternInPlace),接著查找經(jīng)過intern機(jī)制處理的PyStringObject對象,如果發(fā)現(xiàn)有該字符串對應(yīng)的PyStringObject存在,則直接返回該對象,否則把剛剛創(chuàng)建的PyStringObject加入到intern機(jī)制中。由于a和b字符串字面值是一樣的,因此a和b都指向同一個(gè)PyStringObject("python")對象。那么intern內(nèi)部又是一個(gè)什么樣的機(jī)制呢?

[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機(jī)制只處理字符串

2.如果該P(yáng)yStringObject對象已經(jīng)進(jìn)行過intern機(jī)制處理,則直接返回

3.interned其實(shí)一個(gè)字典對象,當(dāng)它為null時(shí),初始化一個(gè)字典對象,否則,看該字典中是否存在一個(gè)key為(PyObject *)s的value,如果存在,那么就把該對象的引用計(jì)數(shù)加1,臨時(shí)創(chuàng)建的那個(gè)對象的引用計(jì)數(shù)減1。否則,把(PyObject *)s同時(shí)作為key和value添加到interned字典中,與此同時(shí)它的引用計(jì)數(shù)減2,這兩個(gè)引用計(jì)數(shù)減2是因?yàn)楸籭nterned字典所引用,但這兩個(gè)引用不作為垃圾回收的判斷依據(jù),否則,字符串對象永遠(yuǎn)都不會被垃圾回收器收集了。


上述代碼中,給b賦值為"python"后,系統(tǒng)中創(chuàng)建了幾個(gè)PyStringObject對象呢?答案是:2,在創(chuàng)建b的時(shí)候,一定會有一個(gè)臨時(shí)的PyStringObject作為字典的key在interned中查找是否存在一個(gè)PyStringObject對象的值為"python"。

字符串的緩沖池

字符串除了有intern機(jī)制緩存字符串之外,字符串還有一種專門的短字符串緩沖池characters。用于緩存字符串長度為1的PyStringObject對象。

static PyStringObject *characters[UCHAR_MAX + 1]; //UCHAR_MAX = 255

創(chuàng)建長度為1的字符串時(shí)流程:

...
else if (size == 1) {
PyObject *t = (PyObject *)op;
PyString_InternInPlace(&t);
op = (PyStringObject *)t;
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
  • 首先創(chuàng)建一個(gè)PyStringObject對象。
  • 進(jìn)行intern操作
  • 將PyStringObject緩存到characters中
  • 引用計(jì)數(shù)增1

總結(jié):

1. 字符串用PyStringObject表示

2. 字符串屬于變長對象

3. 字符串屬于不可變對象

4. 字符串用intern機(jī)制提高python的效率

5. 字符串有專門的緩沖池存儲長度為1的字符串對象

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • python使用super()出現(xiàn)錯(cuò)誤解決辦法

    python使用super()出現(xiàn)錯(cuò)誤解決辦法

    這篇文章主要介紹了python使用super()出現(xiàn)錯(cuò)誤解決辦法的相關(guān)資料,對于TypeError: must be type, not classobj的錯(cuò)誤進(jìn)行處理,需要的朋友可以參考下
    2017-08-08
  • Python之NumPy(axis=0 與axis=1)區(qū)分詳解

    Python之NumPy(axis=0 與axis=1)區(qū)分詳解

    這篇文章主要介紹了Python之NumPy(axis=0 與axis=1)區(qū)分詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • 深入學(xué)習(xí)Python中的裝飾器使用

    深入學(xué)習(xí)Python中的裝飾器使用

    @這個(gè)操作符讓裝飾器在Python代碼中非常醒目,而裝飾器的運(yùn)用中也包含著很多Python編程中的高級技巧,這里我們就來共同深入學(xué)習(xí)Python中的裝飾器使用
    2016-06-06
  • python3反轉(zhuǎn)字符串的3種方法(小結(jié))

    python3反轉(zhuǎn)字符串的3種方法(小結(jié))

    這篇文章主要介紹了python3反轉(zhuǎn)字符串的3種方法(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Python 跨.py文件調(diào)用自定義函數(shù)說明

    Python 跨.py文件調(diào)用自定義函數(shù)說明

    這篇文章主要介紹了Python 跨.py文件調(diào)用自定義函數(shù)說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-06-06
  • Python實(shí)現(xiàn)獲取照片的地理定位信息

    Python實(shí)現(xiàn)獲取照片的地理定位信息

    這篇文章主要為大家詳細(xì)介紹了如何使用 Python 的 PIL(Python Imaging Library)庫實(shí)現(xiàn)從 JPEG 圖像中獲取經(jīng)緯度信息,需要的可以參考一下
    2023-05-05
  • python基礎(chǔ)之并發(fā)編程(一)

    python基礎(chǔ)之并發(fā)編程(一)

    這篇文章主要介紹了詳解python的并發(fā)編程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-10-10
  • python中的閉包和裝飾器的使用示例

    python中的閉包和裝飾器的使用示例

    閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù),例如在javascript中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以閉包可以理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù),這篇文章主要介紹了python中的閉包和裝飾器的使用,需要的朋友可以參考下
    2022-11-11
  • Python中atexit模塊的基本使用示例

    Python中atexit模塊的基本使用示例

    這篇文章主要介紹了Python中atexit模塊的基本使用示例,示例代碼基于Python2.x版本,注意其和Python3的兼容性,需要的朋友可以參考下
    2015-07-07
  • 使用?OpenCV?開發(fā)虛擬鍵盤的方法

    使用?OpenCV?開發(fā)虛擬鍵盤的方法

    OpenCV是一個(gè)強(qiáng)大的圖像處理工具,用于機(jī)器學(xué)習(xí)、圖像處理等的跨平臺開源庫,用于開發(fā)實(shí)時(shí)計(jì)算機(jī)視覺應(yīng)用程序,本文重點(diǎn)給大家介紹使用?OpenCV?開發(fā)虛擬鍵盤的方法,感興趣的朋友一起看看吧
    2021-11-11

最新評論