Python之code物件與pyc檔案(三)

2022-04-05 08:32:42 字數 4796 閱讀 5068

上一節:python之code物件與pyc檔案(二)

向pyc寫入字串

在了解python如何將字串寫入到pyc檔案的機制之前,我們先來了解一下結構體wfile:

marshal.c

typedef struct  wfile;

wfile可以看做是乙個對file *的簡單包裝,但在wfile中,出現了乙個奇特的strings域,這個域是python向pyc檔案中寫入字串或從中讀取字串的關鍵所在,當向pyc中寫入時,strings會指向乙個pydictobject物件,而從pyc中讀出時,strings則會指向乙個pylistobject物件

我們再次回到pymarshal_writeobjecttofile這個函式

marshal.c

void pymarshal_writeobjecttofile(pyobject *x, file *fp, int version)

可以看到在開始寫入物件之前,wfile的strings就已經指向乙個pydictobject物件了

marshal.c

else if (pystring_check(v)) 

//<3>intern字串的首次寫入

else

w_byte(type_interned, p);

} }//<4>寫入普通string

else

n = pystring_get_size(v);

if (n > int_max)

//<5>寫入字串的長度

w_long((long)n, p);

w_string(pystring_as_string(v), (int)n, p);

}

向pyc寫入乙個字串時,可能分為3種情況:

寫入乙個普通的字串,先寫入字串的型別標識type_string,然後呼叫w_long寫入字串長度,最後通過w_string寫入字串本身,這一切都在<4>和<5>這裡完成的。除了普通字串外,python還會碰到在以後載入pyc檔案時需要進行intern操作的字串。對於這種字串又分為首次寫入和非首次寫入。

這裡簡略介紹一下intern機制,python有乙個字串緩衝池,當要生成乙個字串時,python會檢查緩衝池是否已有現成的字串,如果有則返回,如果沒有則將字串儲存在緩衝池,但python並非對所有的字串都做緩衝檢查(即intern機制)

我們宣告a和b兩個變數,並賦予相同的字串,然後檢視它們的位址,它們的位址是相同的,這正是因為intern機制起了作用

>>> a = "helloworld"

>>> b = "helloworld"

>>> id(a)

139894353361584

>>> id(b)

139894353361584

我們再來看另外乙個例子:

>>> c = "hello world"

>>> d = "hello world"

>>> id(c)

139894353361536

>>> id(d)

139894353361728

如上,c和d之於a和b,無非就是多了乙個空格,但我們發現,c和d的位址明顯不一樣,為什麼會造成這樣的差異呢?原因是因為intern機制預設只對簡單字元進行處理,簡單字元即"0123456789abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz"構成的字串

另外,對於計算得出的字串,也不做intern

>>> lst = ["a", "b", "c"]

>>> e = "".join(lst)

>>> f = "".join(lst)

>>> id(e)

139894353351824

>>> id(f)

139894353353104

現在,我們知道什麼是intern機制,再次回到w_object這個方法中的寫入字串處,如果字串是可以被intern機制處理的字串,那麼分為首次寫入和非首次寫入。

之前說過,在寫入的時候,wfile中的strings指向乙個pydictobject物件,這個物件中,實際上維護著(pystringobject,pyintobject)這樣的對映關係,key為字串,而value則是這個字串是第幾個被加入到wfile.strings中的字串,更確切的說是第幾個被寫入到pyc檔案中的intern字串

python為什麼需要這個pyintobject物件的值呢?假設我們要向pyc檔案寫入3個字串:"hello"、"world"、"hello",如果我們沒有intern機制,也沒有strings這個pydictobject物件,我們只管埋頭往pyc檔案裡寫字串,那麼pyc檔案會長什麼樣子呢?如下:

pyc檔案

(type_string,5,hello)

(type_string,5,world)

(type_string,5,hello)

上面的pyc檔案儲存著3個值,這個值裡第乙個元素是型別,為字串型別type_string,第二個元素為字串的長度,第三個為字串本身的值,我們會發現,第2行和第4行重複了,如果源**存在大量重複的字串,那麼這樣的做法無疑會使得整個pyc檔案存在大量冗餘的資訊。python作為一門優雅的語言,顯然不允許這樣的操作存在,那麼intern機制和strings指向的pydictobject就派上用場了

現在我們有了intern機制和strings指向的pydictobject物件,我們依舊往pyc檔案寫入"hello"、"world"、"hello"這3個值,由於這3個字串是可以啟用intern機制,所以這裡我們也看一下strings這個pydictobject物件中的結果:

string

hello

0world

1之前我們說過,strings會儲存寫入pyc檔案的字串是第幾個,所以strings儲存的內容如上,那麼pyc檔案儲存的內容又是怎樣的呢?

pyc檔案

(type_string,5,hello)

(type_string,5,world)

(type_stringref,0)

現在這個pyc檔案的內容,較之前的pyc內容有一點不一樣了,就是第三行所儲存的內容,我們來看一下新pyc檔案的第三行內容:(type_stringref,0),這裡儲存的型別不再是type_string,也不再儲存字串的長度和字串本身,type_stringref這個型別代表在解析pyc檔案時,對應的值要去strings中查詢,而它的索引值即為0。等一下,好像有點不對?在上面我們的介紹中,strings這個pydictobject物件裡何曾有過0這個索引?不要急,且看我後面慢慢道來

我們都知道,pydictobject中儲存的是(字串,第幾次寫入pyc檔案)這樣的形式,在載入pyc檔案時,同樣用到wfile這個結構體,而且同樣需要用到strings這個變數,不過這時候的strings不再是pydictobject,而是pylistobject。strings這個變數非常有意思,在寫入物件時,它是pydictobject,在載入pyc檔案讀取物件時,它是pylistobject。之前pydictobject的value,即為整型值,現在作為pylistobject的索引值,而索引值對應的內容即為字串,這樣,當python載入pyc檔案時,讀到乙個type_stringref型別的元素和乙個索引值,就知道要去strings這個pylistobject查詢對應索引值所儲存的內容

現在,我們再來看下pyc檔案載入的方法

marshal.c

typedef wfile rfile;

pyobject *pymarshal_readobjectfromfile(file *fp)

我們看到,在載入pyc檔案時,rf依然是wfile物件,且這時候strings不再是pydictobject,而是pylistobject,而r_object可以視為上面w_object的逆運算

遺失的pycodeobject

在python之code物件與pyc檔案(一)這個章節中,我們說過demo.py會生成3個pycodeobject:

demo.py

class a:

pass

def func():

pass

a = a()

func()

而在pymarshal_writeobjecttofile這個方法中,我們看到,這個方法只會對乙個pycodeobject物件進行操作,那麼另外兩個pycodeobject物件呢?事實上,另外兩個pycodeobject存在於乙個pycodeobject之中,即demo.py本身是乙個大的pycodeobject,而class a和def func這兩個pycodeobject存在於demo.py對應的pycodeobject裡面。我們可以用co_consts來檢視另外兩個pycodeobject: 

>>> source = open("demo.py").read()

>>> co = compile(source, "demo.py", "exec")

>>> co.co_consts

('a',,, none, ())

果然如我們前面所說,co_consts會返回乙個元組,元組中包含的就是class a和def func這兩個pycodeobject,code型別中依舊存在很多我們可以**的物件,如:co_nlocals(code block中區域性變數的個數,包括其位置引數的個數)、co_varnames(code block中的區域性變數名集合),感興趣的同學可以再去多做研究一下,這裡不再乙個個展示

python之命名空間與函式物件

目錄作用域 函式名的多種用法 函式的巢狀 命名空間就是變數名與變數值繫結關係的地方。比如對於x 1,1存放於記憶體空間,而x與1的對應關係儲存在命名空間中。命名空間分為三類 內建命名空間 全域性命名空間 區域性命名空間。內建命名空間儲存的是python直譯器提前給我們定義好的,像len print ...

Python物件導向之物件成員

任務 請修改右側 begin end 之間的 使其可以正常執行。該 的目的是求輸入資料的階乘。請在下面的begin end之間按照注釋中給出的提示編寫正確的 begin class factorial def init self,num self.num num defget value self ...

python物件導向 類與物件

嗯,本學期開始學python物件導向的內容了,唔,前面的內容會在後期有時間慢慢補的。類與物件 我生活中有這樣一句話叫 物以類聚,人以群分 重點是前面那句,什麼是類呢,就是一類事物,比如人類 動物類 這是乙個大的範圍 類是封裝物件的屬性和行為的載體,反過來說,具有相同屬性和行為的一類實體被稱為類 而物...