本文介紹了 python 內部是如何管理字串物件,以及字串查詢操作是如何實現的。
pystringobject 結構體
python 中的字串物件在內部對應乙個名叫 pystringobject 的結構體。「ob_shash」 對應字串經計算過的 hash值, 「ob_sval」 指向一段長度為 「ob_size」 的字串,且該字串以『null』結尾(為了相容c)。「ob_sval」的初始大小為1個位元組,且 ob_sval[0]=0(對應空字串)。若你還想知道「ob_size」被定義的位置,可以看一看 object.h 標頭檔案中 pyobject_var_head 對應部分。「ob_sstate」 用來指示某個字串是否已經存在於intern機制對應的字典中,後面我們會再次提到這一點。
typedefstructpystringobject;
字串物件的建立
如下所示,當將乙個新的字串賦給乙個變數時,發生了什麼?
1>>> s1 = 'abc'
執行以上**時,內部的 c 函式 「pystring_fromstring」 將被呼叫並生成類似下面的偽**:
arguments:stringobject:'abc'
returns:python stringobjectwithob_sval='abc'
pystring_fromstring(string):
size=length of string
allocate stringobject+sizefor'abc'.ob_sval will be ofsize:size+1
copystringto ob_sval
returnobject
每次用到新的字串時,都將分配乙個字串物件。
共享字串物件
python 有乙個優雅的特性,就是變數之間的短字串是共享的,這一特性可以節省所需的記憶體空間。短字串就是那些長度為 0 個或者 1 個位元組的字串。而全域性變數 「interned」 對應乙個用於索引這些短字串的字典。陣列 「characters」 也可用於索引那些長度為 1 個位元組的字串,比如單個字母。後面我們將看到陣列 「characters」 是如何被使用的。
staticpystringobject*characters[uchar_max+1];
staticpyobject*interned;
下面一起看看:當你在 python 指令碼中將乙個短字串賦值給乙個變數時,背後發生了哪些事情。
staticpystringobject*characters[uchar_max+1];
staticpyobject*interned;
內容為 『a』 的字串物件將被新增到 「interned」 字典中。字典中鍵(key)是乙個指向該字串物件的指標,而對應的值 就是乙個相同的指標。在陣列 「characters」 中,這一新的字串物件在偏移量為 97 的位置被引用,因為字元 『a』 的ascii碼值便是 97。變數 「s2」 也指向了這一字串物件。
而,當另外乙個變數也被相同的字串 『a』 賦值時,又會如何呢?
1>>> s3 = 'a'
上述**執行後,將返回之前已建立的內容相同的字串物件。因此,『s1』 和 『s3』 兩個變數都將指向同乙個字串物件。 陣列 「characters」 便是用於檢測字串 『a』 是否已經存在,若存在,則返回指向該字串物件的指標。
if(size==1&&(op=characters[*str&uchar_max])!=null)
return(pyobject*)op;
下面我們新建乙個內容為 『c』 的短字串:
1>>> s4 = 'c'
那麼,我們將得到如下結果:
我們還能發現,當按照下面 python 指令碼中的方式對乙個字串元素進行訪問時,陣列 「characters」 仍有用武之地。
>>>s5='abc'
>>>s5[0]
'a'上面第二行**中,返回的是陣列 「characters」 偏移量為 97 的位置內的指標元素,而非新建乙個值為 『a』的字串。當我們訪問某個字串中的元素時,乙個名叫 「string_item」 d的函式將被呼叫,下方給出了函式體**。其中,引數 『a』 便對應著字串 「abc」,而引數 『i』 便是訪問陣列的索引值(本例中便為 0 ),函式返回的是指向某個字串物件的指標。
staticpyobject*
string_item(pystringobject*a,registerpy_ssize_ti)
charpchar;
pyobject*v;
pchar=a->ob_sval[i];
v=(pyobject*)characters[pchar&uchar_max];
if(v==null)
//allocatestring
elseelse{
for(i=0;i
if(s[i]==p[0])
returni;
return-1;
考慮其他情況,比如 m > 1。首先建立乙個壓縮的boyer-moore delta 1 table(對應bm演算法中的壞字元規則),在此過程中需要宣告兩個變數:「mask」 和 「skip」。
「mask」 是乙個 32 位的位掩碼(bitmask),將其最低的 5 個特徵位作為開關位。該掩碼是通過和模式串 「p」 進行操作產生的。它設計成乙個布隆過濾器(bloom filter),用於檢測乙個字元是否出現在當前字串中。這種機制使查詢操作十分迅速,但是存在偽正的情況(false positives)。關於布隆過濾器,你想有更多了解的話可以看看 這裡 。對於本例,下方說明了位掩碼具體是如何產生的。
mlast=m-1
/*processpattern[:-1]*/
for(mask=i=0;i
mask|=(1<
/*processpattern[-1]outside theloop*/
mask|=(1<
字串 「p」 的第乙個字元為 『a』。字元『a』的二進位制表示為 97 = 1100001。保留最低的 5 個特徵位,我們得到了 00001,因此變 「mask」 初次被設定為 10(1 << 1)。當整個字串 「p」 都經過處理後,mask 值為 1110。那麼,我們應該如何使用這個位掩碼呢?通過下方這行**,我們用其來檢測字元 「c」 位於字串 「p」 哪個位置。
if ((mask & (1 << (c & 0x1f))))
那麼,字元 『a』 在字串 「p」(『abcab』)中是否存在呢?1110 & (1 << (『a』 & 0x1f)) 運算結果的值是否為 true 呢?由於 1110 & (1 << (『a』 & 0x1f)) = 1110 & 10 = 10,可知 『a』 確實存在於 『abcab』。當檢測字元 『d』時,我們得到的是 false,對於其他字元(從 『e』 到 『z』)也是同樣結果。因此,在本例中此類過濾器表現十分出眾。 變數 「skip」 對應目標字元在主字串中最後乙個成功匹配的字元的索引位置(從後向前匹配)。假若模式串的最後乙個匹配字元在主字串中不存在,則 「skip」 值為 模式串 「p」 的長度減去 1。本例中,模式串最後乙個為匹配字元位 『b』,由於其在主串查詢的當前位置向後跳兩個字元後能夠匹配到,因此變數 「skip」 的值為2。這個變數應用於一種名叫壞字元跳躍(bad-character skip)的規則。在如下示例中,p = 『abcab』,s = 『adcabcaba』。從主串 「s」 的 4 號索引位置(從 0 開始計算)開始匹配,若字元匹配成功則向前繼續匹配。第乙個匹配失敗的索引位置為 1(此處 『b』 不等於 『d』)。我們可以看到,在模式串和主串最開始匹配的末端位置往後數三個字元,主串中也有乙個 『b』,而字元 『c』 也存在於 「p」 中,因此我們跳過了隨後的 『b』。
下面,看下查詢操作的迴圈部分(真實**為 c 實現,而非 python):
fori=0ton-m=13:
ifs[i+m-1]==p[m-1]:
ifs[i:i+mlast]==p[0:mlast]:
returni
ifs[i+m]notinp:
i+=m
else:
i+=skip
else:
ifs[i+m]notinp:
i+=m
return-1
「s[i+m] not in p」 這行測試**是基於位掩碼實現的,「i += skip」 便對應壞字元跳躍。當主串下乙個待匹配的字元在 「p」 中並未找到時,則執行 「i += m」 這行**。
深入了解Python的繼承
目錄 繼承的概念 子類 擁有 父類 的所有 方法 和 屬性 不使用繼承類 class animal def eat self print 吃 def drink self print 喝 def run self print 跑 def sleep self print 睡 class dog de...
深入了解python的函式引數
目錄 這是乙個求等差數列和的函式,使用必需要傳入乙個引數n,這就是位置引數 def sum n sum 0 i 1while i n sum i i 1 return sum result sum 100 print result 預設引數就是,我在函式中已經將乙個引數提前設定好了值,如果你沒有傳入...
深入了解Python中的變數
目錄 變數,英文叫做 variable。在 電腦科學概述 中是這樣定義的,高階程式語言允許使用描述性的名字指向主儲存器中的位置,而不必再使用數字位址,這樣的名字稱為變數 variable 之所以是這樣取名是因為,隨著程式的執行,只要改變儲存在這個位置裡的值,那麼與改名字相聯絡的值就會改變。從形式上看...