當前位置:
首頁 > 知識 > Python 源碼閱讀: String

Python 源碼閱讀: String

(點擊

上方藍字

,快速關注我們)




來源:伯樂在線 - 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

)

分配內存空間

,

然後逐一拷貝

.

一次內存操作

.




相關閱讀




  • 《Python 源碼閱讀:類型》



  • 《Python 源碼閱讀:對象》




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

Python 中的屬性訪問與描述符
Python 源碼閱讀:對象
構建多層感知器神經網路對數字圖片進行文本識別
Gevent 調度流程解析
為什麼你應該學 Python ?

TAG:Python開發者 |