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 源碼閱讀:對象
※構建多層感知器神經網路對數字圖片進行文本識別
※Gevent 調度流程解析
※為什麼你應該學 Python ?
TAG:Python開發者 |