




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第Python內(nèi)建類型bytes深入理解目錄引言1bytes和str之間的關(guān)系2bytes對象的結(jié)構(gòu):PyBytesObject3bytes對象的行為3.1PyBytes_Type3.2bytes_as_sequence4字符緩沖池
引言
深入認識Python內(nèi)建類型這部分的內(nèi)容會從源碼角度為大家介紹Python中各種常用的內(nèi)建類型。
在我們?nèi)粘5拈_發(fā)中,str是很常用的一個內(nèi)建類型,與之相關(guān)的我們比較少接觸的就是bytes,這里先為大家介紹一下bytes相關(guān)的知識點,下一篇博客再詳細介紹str的相關(guān)內(nèi)容。
1bytes和str之間的關(guān)系
不少語言中的字符串都是由字符數(shù)組(或稱為字節(jié)序列)來表示的,例如C語言:
charstr[]="HelloWorld!";
由于一個字節(jié)最多只能表示256種字符,要想覆蓋眾多的字符(例如漢字),就需要通過多個字節(jié)來表示一個字符,即多字節(jié)編碼。但由于原始字節(jié)序列中沒有維護編碼信息,操作不慎就很容易導(dǎo)致各種亂碼現(xiàn)象。
Python提供的解決方法是使用Unicode對象(也就是str對象),Unicode口語表示各種字符,無需關(guān)心編碼。但是在存儲或者網(wǎng)絡(luò)通訊時,字符串對象需要序列化成字節(jié)序列。為此,Python額外提供了字節(jié)序列對象bytes。
str和bytes的關(guān)系如圖所示:
str對象統(tǒng)一表示一個字符串,不需要關(guān)心編碼;計算機通過字節(jié)序列與存儲介質(zhì)和網(wǎng)絡(luò)介質(zhì)打交道,字節(jié)序列用bytes對象表示;存儲或傳輸str對象時,需要將其序列化成字節(jié)序列,序列化過程也是編碼的過程。
2bytes對象的結(jié)構(gòu):PyBytesObject
C源碼:
typedefstruct{
PyObject_VAR_HEAD
Py_hash_tob_shash;
charob_sval[1];
/*Invariants:
*ob_svalcontainsspacefor'ob_size+1'elements.
*ob_sval[ob_size]==0.
*ob_shashisthehashofthestringor-1ifnotcomputedyet.
}PyBytesObject;
源碼分析:
字符數(shù)組ob_sval存儲對應(yīng)的字符,但是ob_sval數(shù)組的長度并不是ob_size,而是ob_size+1.這是Python為待存儲的字節(jié)序列額外分配了一個字節(jié),用于在末尾處保存\0,以便兼容C字符串。
ob_shash:用于保存字節(jié)序列的哈希值。由于計算bytes對象的哈希值需要遍歷其內(nèi)部的字符數(shù)組,開銷相對較大。因此Python選擇將哈希值保存起來,以空間換時間(隨處可見的思想,hh),避免重復(fù)計算。
圖示如下:
3bytes對象的行為
3.1PyBytes_Type
C源碼:
PyTypeObjectPyBytes_Type={
PyVarObject_HEAD_INIT(PyType_Type,0)
"bytes",
PyBytesObject_SIZE,
sizeof(char),
//...
bytes_as_number,/*tp_as_number*/
bytes_as_sequence,/*tp_as_sequence*/
bytes_as_mapping,/*tp_as_mapping*/
(hashfunc)bytes_hash,/*tp_hash*/
//...
數(shù)值型操作bytes_as_number:
staticPyNumberMethodsbytes_as_number={
0,/*nb_add*/
0,/*nb_subtract*/
0,/*nb_multiply*/
bytes_mod,/*nb_remainder*/
bytes_mod:
staticPyObject*
bytes_mod(PyObject*self,PyObject*arg)
if(!PyBytes_Check(self)){
Py_RETURN_NOTIMPLEMENTED;
return_PyBytes_FormatEx(PyBytes_AS_STRING(self),PyBytes_GET_SIZE(self),
arg,0);
可以看到,bytes對象只是借用%運算符實現(xiàn)字符串格式化,并不是真正意義上的數(shù)值運算(這里其實和最開始的分類標(biāo)準(zhǔn)是有點歧義的,按標(biāo)準(zhǔn)應(yīng)該再分一個格式型操作,不過靈活處理也是必須的):
b'msg:a=%d,b=%d'%(1,2)
b'msg:a=1,b=2'
序列型操作bytes_as_sequence:
staticPySequenceMethodsbytes_as_sequence={
(lenfunc)bytes_length,/*sq_length*/
(binaryfunc)bytes_concat,/*sq_concat*/
(ssizeargfunc)bytes_repeat,/*sq_repeat*/
(ssizeargfunc)bytes_item,/*sq_item*/
0,/*sq_slice*/
0,/*sq_ass_item*/
0,/*sq_ass_slice*/
(objobjproc)bytes_contains/*sq_contains*/
bytes支持的序列型操作包括以下5個:
bytes_length:查詢序列長度bytes_concat:將兩個序列合并為一個bytes_repeat:將序列重復(fù)多次bytes_item:取出給定下標(biāo)的序列元素bytes_contains:包含關(guān)系判斷
關(guān)聯(lián)型操作bytes_as_mapping:
staticPyMappingMethodsbytes_as_mapping={
(lenfunc)bytes_length,
(binaryfunc)bytes_subscript,
可以看到bytes支持獲取長度和切片兩個操作。
3.2bytes_as_sequence
這里我們主要介紹以下bytes_as_sequence相關(guān)的操作
bytes_as_sequence中的操作都不復(fù)雜,但是會有一個陷阱,這里我們以bytes_concat操作來認識一下這個問題。C源碼如下:
/*ThisisalsousedbyPyBytes_Concat()*/
staticPyObject*
bytes_concat(PyObject*a,PyObject*b)
Py_bufferva,vb;
PyObject*result=NULL;
va.len=-1;
vb.len=-1;
if(PyObject_GetBuffer(a,va,PyBUF_SIMPLE)!=0||
PyObject_GetBuffer(b,vb,PyBUF_SIMPLE)!=0){
PyErr_Format(PyExc_TypeError,"can'tconcat%.100sto%.100s",
Py_TYPE(b)-tp_name,Py_TYPE(a)-tp_name);
gotodone;
/*Optimizeendcases*/
if(va.len==0PyBytes_CheckExact(b)){
result=b;
Py_INCREF(result);
gotodone;
if(vb.len==0PyBytes_CheckExact(a)){
result=a;
Py_INCREF(result);
gotodone;
if(va.lenPY_SSIZE_T_MAX-vb.len){
PyErr_NoMemory();
gotodone;
result=PyBytes_FromStringAndSize(NULL,va.len+vb.len);
if(result!=NULL){
memcpy(PyBytes_AS_STRING(result),va.buf,va.len);
memcpy(PyBytes_AS_STRING(result)+va.len,vb.buf,vb.len);
done:
if(va.len!=-1)
PyBuffer_Release(va);
if(vb.len!=-1)
PyBuffer_Release(vb);
returnresult;
bytes_concat源碼大家可自行分析,這里直接以圖示形式來展示,主要是為了說明其中的陷阱。
圖示如下:
Py_buffer提供了一套操作對象緩沖區(qū)的統(tǒng)一接口,屏蔽不同類型對象的內(nèi)部差異bytes_concat則將兩個對象的緩沖區(qū)拷貝到一起,形成新的bytes對象
上述的拷貝過程是比較清晰的,但是這里隱藏著一個問題數(shù)據(jù)拷貝的陷阱。
以合并3個bytes對象為例:
a=b'abc'
b=b'def'
c=b'ghi'
result=a+b+c
result
b'abcdefghi'
本質(zhì)上這個過程會合并兩次
t=a+b
result=t+c
在這個過程中,a和b的數(shù)據(jù)都會被拷貝兩遍,圖示如下:
不難推出,合并n個bytes對象,頭兩個對象需要拷貝n-1次,只有最后一個對象不需要重復(fù)拷貝,平均下來每個對象大約要拷貝n/2次。因此,下面的代碼:
result=b''
forbinsegments:
result+=s
效率是很低的。我們可以使用join()來優(yōu)化:
result=b''.join(segments)
join()方法是bytes對象提供的一個內(nèi)建方法,可以高效合并多個bytes對象。join方法對數(shù)據(jù)拷貝進行了優(yōu)化:先遍歷待合并對象,計算總長度;然后根據(jù)總長度創(chuàng)建目標(biāo)對象;最后再遍歷待合并對象,逐一拷貝數(shù)據(jù)。這樣一來,每個對象只需要拷貝一次,解決了重復(fù)拷貝的陷阱。(具體源碼大家可以自行去查看)
4字符緩沖池
和小整數(shù)一樣,字符對象(即單字節(jié)的bytes對象)數(shù)量也很少,只有256個,但使用頻率非常高,因此以空間換時間能明顯提升執(zhí)行效率。字符緩沖池源碼如下:
staticPyBytesObject*characters[UCHAR_MAX+1];
下面我們從創(chuàng)建bytes對象的過程來看一下字符緩沖池的使用:PyBytes_FromStringAndSize()函數(shù)是負責(zé)創(chuàng)建bytes對象的通用接口,源碼如下:
PyObject*
PyBytes_FromStringAndSize(constchar*str,Py_ssize_tsize)
PyBytesObject*op;
if(size0){
PyErr_SetString(PyExc_SystemError,
"NegativesizepassedtoPyBytes_FromStringAndSize");
returnNULL;
if(size==1str!=NULL
(op=characters[*strUCHAR_MAX])!=NULL)
#ifdefCOUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return(PyObject*)op;
op=(PyBytesObject*)_PyBytes_FromSize(size,0);
if(op==NULL)
returnNULL;
if(str==NULL)
return(PyObject*)op;
memcpy(op-ob_sval,str,size);
/*shareshortstrings*/
if(size==1){
characters[*strUCHAR_MAX]=op;
Py_INCREF(op);
return(PyObject*)op;
其中涉及字符緩沖區(qū)維護的關(guān)鍵步驟如下:
第10~17行:如果創(chuàng)建的對象為單字節(jié)對象,會先在characters數(shù)組的對應(yīng)序號判斷是否已經(jīng)有相應(yīng)的對象存儲在了緩沖區(qū)中,如果有則直接取出
第28~31行:如果創(chuàng)建的對象為單字節(jié)對象,并且之前已經(jīng)判斷了不在緩沖區(qū)中,則將其放入字符緩沖池的對應(yīng)位置
由此可見,當(dāng)Python程序開始運行時,字符緩沖池是空的。隨著單字節(jié)bytes對象的創(chuàng)建,緩沖池中的對象就慢慢多了起來。當(dāng)緩沖池已緩存b1、b2、b3、ba、bb、bc這幾個字符時,內(nèi)部結(jié)構(gòu)如下:
示例:
注:這里大家可能在IDLE和PyCharm中獲得的結(jié)果不一致,這個問題在之前的博客中也提到過,查閱資料后得到的結(jié)論是:IDLE運行和PyCharm運行的方式不同。這里我將PyCharm代碼對應(yīng)的代碼對象反編譯的結(jié)果展示給大家,但我對IDLE的認識還比較薄弱,以后有機會再給大家詳細補充這個知識(抱拳~)。
這里大家還是先以認識字符緩沖區(qū)這個概念為主,當(dāng)然字節(jié)碼的相關(guān)知識掌握好了也是很有幫助的。以下是PyCharm運行的結(jié)果:
以下操作的相關(guān)講解可以看這篇博客:Python程序執(zhí)行過程與字節(jié)碼
示例1:
下面我們來看一下反編譯的結(jié)果:(下面的文件路徑我省略了,大家自己試驗的時候要輸入正確的路徑)
text=open('D:\\...\\test2.py').read()
result=compile(text,'D:\\...\\test2.py','exec')
importdis
dis.dis(result)
10LOAD_CONST0(b'a')
2STORE_NAME0(a)
24LOAD_CONST0(b'a')
6STORE_NAME1(b)
38LOAD_NAME2(print)
10LOAD_NAME0(a)
12LOAD_NAME1(b)
14IS_OP0
16CALL_FUNCTION1
18POP_TOP
20LOAD_CONST1(None)
22RETURN_VALUE
可以很清晰地看到,第5行和第8行的LOAD_CONST指令操作的都是下標(biāo)為0的常量ba,因此此時a和b對應(yīng)的是同一個對象,我們打印看一下:
result.co_consts[0]
示例2:
為了確認只會緩存單字
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 機電工程理論分析試題及答案
- 機電工程項目資金管理試題及答案
- 文化政策在經(jīng)濟轉(zhuǎn)型中的角色分析試題及答案
- 機電工程實施方案及試題答案
- 信息系統(tǒng)項目管理師精準(zhǔn)備考試題及答案
- 軟考網(wǎng)絡(luò)工程師復(fù)習(xí)計劃分享試題及答案
- 創(chuàng)新型企業(yè)制度的構(gòu)建與實踐
- 公共政策與可持續(xù)發(fā)展的成功實踐案例試題及答案
- 政策執(zhí)行中的監(jiān)督與評估考題及答案
- 公共事業(yè)與社會政策的互動關(guān)系探討試題及答案
- 員工工資條模板
- 云南省楚雄州2023年六年級數(shù)學(xué)第二學(xué)期期末達標(biāo)測試試題含解析
- 2023-2024人教版小學(xué)5五年級數(shù)學(xué)下冊(全冊)教案
- 交付經(jīng)理工作職責(zé)
- 國際檔案日宣傳教育課件(帶內(nèi)容)
- 基于PLC的藥房取藥系統(tǒng)設(shè)計
- 安徽省汽車維修行業(yè)車輛維修合同
- GB/T 16447-2004煙草及煙草制品調(diào)節(jié)和測試的大氣環(huán)境
- 公司勞務(wù)派遣人員工資薪酬發(fā)放暫行規(guī)定
- 建筑大師伊東豐雄簡介及作品集課件
- 公司二次經(jīng)營創(chuàng)效管理實施細則
評論
0/150
提交評論