《Python源碼剖析》讀書筆記

《Python源碼剖析》電子書下載 http://download.csdn.net/detail/xiarendeniao/5130403


Python源碼在官網有下載鏈接,用ctags -R *來建符號鏈接,在閱讀電子書的過程中用vi -t functionName/varName方便的查看對應源碼

PyObject

Python對象機制的基石,Python中所有對象都擁有PyObject這一部分內容(且在對象所佔內存的開頭部分)

PyObject其實就是一個引用計數(int)和一個類型對象指針(PyTypeObject* ob_type)組成的結構體

PyObject*作爲一個泛型指針在Python內部傳遞,所有對象的指針都使用PyObject*,具體是什麼類型可以從指針所指對象的ob_type域(Python利用該域實現多態)動態判斷


PyVarObject

PyVarObject是變長對象(包含可變長度數據的對象)的開頭

變長對象(PyStringObject等)的不同對象佔用的內存大小可能不一樣,定長對象(PyIntObject等)的不同對象佔用的內存是一樣的

PyVarObject是對PyObject的擴展,由PyObject和一個元素個數(int)組成


PyTypeObject

類型對象,內部成員記錄該類型的名稱、各種操作函數(如構造函數、加減數值運算、大小比較等)等,詳見PyTypeObject的C語言定義

是Python中對面向對象理論中"類"這個概念的實現

在C中用PyTypeObject作爲類型定義出來的各變量(如PyInt_Type)就是面向對象中的"類"

每個"實例對象"的實例(如PyIntbject的一個實例int(5))的ob_type指針指向一個與自己關聯的類型對象的實例(如PyInt_Type),類型對象(PyTypeObject)的實例(如PyInt_Type)的ob_type指針指向實例PyType_Type,PyType_Type(理論上說PyType_Type應該是一個全局唯一對象)的ob_type指針指向它自己(Page25)[圖1-6]

用實例對象作爲類型定義的變量/對象就是我們一般意義上的實例對象(C++中的實例對象)



PyBaseObject_Type

所有對象最頂層的父類(PyInt_Type的tp_base指向PyBaseObject_Type)

PyBaseObject_Type這個對象的ob_type指向PyType_Type


PyIntObject

整數對象

0.查看書中陳列的類型定義/對象創建代碼思路會比較清晰

1.小整數在python初始化的時候創建好放入緩衝池備用,無需在使用時創建對象;小整數的範圍是可配置後編譯的

2.大整數在使用時當場創建對象,然後放入block_list中的某個位置;大整數不再使用時(引用計數減少到零)並沒有釋放其在block_list中的內存,只是加入到free_list鏈表而已,那麼瘋狂創建大整數(創建新的大整數時舊的大整數暫不釋放,使其無法複用內存)是可以導致內存泄露的

3.所有整數對象由block_list+free_list來維護,包括小整數對象池


PyStringObject

字符串對象

1.字符串對象是不可變的變長對象:對象大小不固定(由字符串長度而定;相比而言,整數對象就是定長對象);對象一旦創建,不可修改(和tuple類似)

2.空字符串對象統一由nullstring代替,使用時只用修改這個全局對象的引用計數即可

3.單個字符的字符串對象有緩衝機制,第一次使用的時候創建對象並緩存下來(和小整數不一樣,小整數是python初始化的時候就創建好),下次使用的時候省去了對象創建過程

4.字符串對象支持intern機制,對某個字符串對象使用intern之後該字符串對象就指針就存入到interned這個dict(對於非單個字符的對象,使用intern機制的時候,對象的引用計數不增加,防止對象因引用計數永遠>=2而無法銷燬),當下次創建字符串對象時檢查該字符串是否已經interned,是的話就返回interned中的引用,銷燬新創建的臨時對象(所以,intern機制不能避免新對象的創建,不過確實能減少內存的消耗;intern機制還可以讓字符串比較變得easy,只用比較PyStringObject的指針即可,因爲相同的字符串都由同一個PyStringObject表示着)

5.字符串拼接儘量用join而不要用+:'aa'+'bb'+'cc'需要分配兩次內存(string_concat);''.join(['aa','bb','cc'])只需分配一次內存(string_join);後者效率更高


PyListObject

列表對象

1.PyListObject也有緩衝機制,當對象銷燬(PyListObject的析構函數)的時候只銷毀PyListObject維護的元素列表ob_item(把數組ob_item中每個指針所指對象引用計數減一;把數組ob_item所佔內存釋放),把PyListObject對象放入緩衝池free_lists中,當下次創建新對象的時候直接從緩衝池獲取一個並創建其元素列表即可;如果free_lists已滿(num_free_lists>=MAXFREELISTS)就不緩存而直接銷燬

2.PyListObject跟C++的vector結構類似;內部的內存管理機制也差不多;跟C++中的list卻很不一樣

3.對於可能爲NULL的PyObject指針,減少其引用計數的時候需要用Py_XDECREF

4.當insert/append元素時會調整PyListObject維護的元素列表的內存大小list_resize()(remove的時候貌似不會),調整的條件是"newsize>=allocate||newsize<=allocated/2"


PyDictObject

字典對象

1.關聯容器(元素以鍵值對的形式存在)存儲符合該容器所代表的關聯規則的元素對(同一個PyDcitObject中存儲的key/value類型可以都不一樣,這個跟C++ map那種強類型的東東不一樣哦),要求極高的搜索效率;C++STL中的map基於紅黑樹(RB-tree,平衡二叉樹)實現,搜索時間複雜度O(logN);PyDictObject對搜索效率要求更高,因爲Python字節碼(變量名-變量值,k-v)的運行環境都需要通過PyDictObject來建立;PyDictObject的數據結構採用散列表(hash table),搜索的最佳時間複雜度是O(1)

2.當不同的key通過散列函數(hash function,將key映射成一個整數,可以用整數作爲索引值訪問某塊內存區域)得到相同的散列值(hash value)時,就出現了散列衝突;出現衝突時Python使用探測函數f作二次探測確定下一個候選位置(開放定址法;也可以用開鏈法解決散列衝突);多次探測時形成的鏈路叫“衝突探測鏈”(探測序列),當元素刪除時只能僞刪除,否則會導致該鏈路斷掉,使得其下一次的搜索無法搜到鏈路後半截的元素

3.PyDictEntry是PyDictObject維護的鍵值對;三種狀態Unused/Active/Dummy;Dummy狀態的PyDictEntry爲刪除的產物、可複用

4.PyDictObject創建時會默認開闢PyDict_MINSIZE(8)個元素的內存空間(ma_smalltable數組);當insert操作使得dict的元素超過該大小時纔會申請額外空間

5.和PyListObject類似,PyDictObject也有緩衝池(free_dicts/num_free_dicts/MAXFREEDICTS)

6.搜索策略lookdict/lookdict_string(默認的簡化版搜索函數)是核心,插入/刪除都是基於搜索來實現的,搜索結果:成功 Active PyDictEntry; 失敗 Unused PyDictEntry / Dummy PyDictEntry

7.插入/設置元素後會檢查是否需要調整PyDcitObject元素存儲的內存區域(ma_table所指向的數組;如果指向ma_smalltable就忽略)大小(裝載率ma_fill/(ma_mask+1)大於2/3時調整);調整操作把內存區域縮小或放大都是有可能的;調整操作的過程實質上是把Dummy PyDictEntry丟掉並重構探測鏈(dictresize函數)


[第七章]Python的編譯結果--Code對象和pyc文件

.pyc

二進制文件

import會觸發.pyc文件的生成,在sys.path中找到.py而沒找到.pyc和.dll時python會編譯.py文件(得到PyCodeObject對象)並寫入.pyc文件

python標準庫的py_compile/compilier也可以編譯.py文件

組成:magic number(用於做python版本兼容);.pyc創建時間;PyCodeObject對象

其實就是把內存中PyCodeObject的數據以一定的規則寫入到硬盤上方便下次使用的時候快速加載(寫和讀詳見7.3.1/7.3.2)

PyCodeObject

一個PyCodeObject可以嵌套多個其他的PyCodeObject,被嵌套的對象記錄在co_consts中

一個Code Block對應一個PyCodeObject

包含字節碼指令以及程序所有靜態信息

PyCodeObject.co_code:字節碼指令序列,PyStringObject

PyCodeObject.co_const:常量表,保存Code Block中所有常量,PyTupleObject

PyCodeObject.co_names:符號表,保存Code Block中所有符號,PyTupleObject

python標準庫的dis可以解析PyCodeObject對象(得到字節碼指令信息)

名字空間

符號(名字)的上下文環境,符號的含義取決於和值取決於名字空間

python中module(.py文件)、類、函數都對應着一個獨立的名字空間

一個Code Block對應着一個名字空間、對應着一個PyCodeObject


[第八章]Python虛擬機框架

PyFrameObject

操作系統運行可執行文件時函數之間的調用通過創建新的棧幀完成,一個函數對應一個棧幀,一連串的棧幀組成函數調用棧,一個函數執行完系統把棧指針和幀指針恢復到前一個棧幀

一個PyFrameObject跟一個Code Block (一個PyCodeObject)對應,記錄着執行環境

多個PyFrameObject相鏈模擬操作系統函數調用棧(執行環境鏈)

對象內部包含PyCodeObject、buildin(內建)名字空間、global(全局)名字空間、local(局部)名字空間

PyFrameObject對象內部的"運行時棧"單指運算時所需內存空間(和操作系統的"運行時棧"不同);運行時棧的大小是由python編譯時計算好了存儲在PyCodeObject中的

名字空間

"約束":一個名字(也叫符號/標識符,如變量名、函數名、類名等)和實際對象之間的關聯關係(name,obj),由賦值語句創建(說白了就是變量名和變量值之間的對應關係)

一個PyDictObject對象,存放着"約束"(說白了就是存放着變量名和變量值之間的對應關係)

一個module中可能存在多個名字空間,每個名字空間與一個作用域對應

一個對象的名字空間中的所有名字都稱爲對象的屬性

屬性引用可當做特殊的名字引用,不受LEGB制約,直接到某個對象的名字空間中去查找名字

作用域

一個"約束"(變量名和變量值之間對應關係)起作用的那段Code Block稱爲這個"約束"的作用域

作用域由源程序的文本決定; 可見,python具有靜態作用域(詞法作用域)的性質

個人理解:說白了就是定義變量的代碼所在的Code Block

LGB

python2.2之前的作用域規則

名字引用動作沿着local作用域、global作用域、buildin作用域的順序查找名字對應的"約束"

LEGB

python2.2引入嵌套函數以後的作用域規則

最內嵌套作用域規則:由一個賦值語句引進的名字在這個賦值語句所在的作用域裏是可見(起作用)的,而且在其內部嵌套的每個作用域裏也可見,除非它被嵌套於內部的、引用同樣名字的另一條賦值語句所屏蔽

E:enclosing,閉包,"直接外圍作用域";把內嵌函數對象和內嵌函數Code Block中名字引用的實際約束(上一層函數定義的作用域/上上一層/全局/buildin)捆綁起來的整體叫做閉包

該規則使得:決定python程序行爲更多的是代碼出現位置而不是代碼執行先後時間(內嵌函數對象不管什麼時候執行,它內部引用的名字始終對應閉包裏面的對象)

LEGB VS 閉包

前者是語言設計時的設計策略,形而上的“道”;後者是實現語言的一種方案,形而下的“器”

global

強制命令python對某個名字的引用只參考global名字空間而不用去管LEGB規則

PyInterpreterState

表示進程概念的對象

PyInterpreterState.tstate_head模擬進程環境中的線程集合(由多個PyThreadState對象組成,PyThreadState輪流使用字節碼執行引擎)

PyThreadState

表示線程概念的對象

PyThreadState.frame模擬線程中的函數調用棧(PyFrameObject鏈)

PyEval_EvalFrameEx

操作當前PyFrameObject:順序執行PyFrameObject中PyCodeObject中的字節碼指令,利用PythonFrameObject的運行時棧、PyCodeObject的符號表、PyCodeObject的常量表操作名字空間


[第九章]Python虛擬機中的一般表達式

1.簡單內建對象的創建:根據字節碼指令(LOAD_CONST、STORE_NAME)從PythonCodeObject的常量表和符號表中取出對應數據利用PyFrameObject的運行時棧做臨時存儲空間最終建立變量名和變量值之間的對應關係並存儲到local名字空間中

2.看陳列的字節碼和字節碼對應的C代碼挺有意思

3.數值運算加法(BINARY_ADD)有快速通道(PyIntObject相加/PyStringObject相加)和慢速通道(有大量的類型判斷和尋找對應加法函數的操作);如果程序有大量浮點運算,爲了提高效率可以手動添加對應的快速通道,然後重新編譯Python

4.print >> file('/tmp/d','w'),'data.data.data' 原來print還有這種用法!


[第十章]Python虛擬機中的控制流

[第十一章]Python虛擬機中的函數機制

def f(): ---> MAKE_FUNCTION指令 ---> PyFunction_New(PyObject* code, PyObject* globals) ---> PyFunctionObject對象

f() ---> CALL_FUNCTION指令 ---> call_function(PyObject*** pp_stack, int oparg) ... ---> PyEval_EvalFrameEx() (函數調用過程:創建新的棧幀,在新的棧幀中執行代碼)

PyFunctionObject是對字節碼和global名字空間的一種打包和運輸方式。(global名字空間、默認參數、字節碼指令捆綁在PyFunctionObject這個大包袱中,PyFunctionObject是Python中閉包的具體表現)


C語言中函數是否可調用(可編譯通過)完全是基於源代碼中函數出現的位置做的分析,而Python則依靠的是運行時的名字空間。

Python四種類別的函數參數:

位置參數(positional argument):f(a,b)

鍵參數(key argument):f(a,b=10)

擴展位置參數(excess positional argument):def f(a,b,*list):...

擴展鍵參數(excess key argument):def f(a,b,**keys):...

函數參數中一個參數是位置參數還是鍵參數實際上僅僅是由函數實參的形式所決定的,而與函數定義時的形參沒有任何關係。另,非鍵參數的位置必須在鍵參數之前。

CALL_FUNCTION指令的參數2Byte,低字節記錄位置參數個數,高字節記錄鍵參數個數(因此Python函數最多256個位置參數/鍵參數)。

Python內部把擴展位置參數*list作爲一個局部變量(每個普通位置參數也作爲一個局部變量):def PyFunc(a,b,*list):pass  對於PyFunc(1,2,3,4)的調用,co_argcount=2,co_nlocals=3 (co_argcount/co_nlocals是PyCodeObject的成員,在編譯時確定,所以不管函數如何調用這兩個值不變;所有擴展位置參數被存儲到一個PyListObject對象中)

與上述類似,Python的所有擴展鍵參數被存儲到一個PyDictObject對象中。


位置參數在函數調用過程中如何傳遞和訪問:在調用函數時,Python將函數參數值從左至右壓入到運行時棧中,在fast_function中,又將這些參數依次(從左到右)拷貝到新建的與函數對應的PyFrameObject對象的f_localsplus中。在訪問函數參數時,Python虛擬機沒有按照通常訪問符號的做法,去查什麼名字空間,而是直接通過一個索引(偏移位置)來訪問f_localsplus中存儲的符號對應的值對象。這種通過索引(偏移位置)進行訪問的方法也正是“位置參數”名稱的由來。


通過閱讀函數調用的實現部分(參數的傳遞、默認參數的使用和替換),可以發現函數調用時位置參數必須在鍵參數的左邊,而鍵參數跟鍵參數之間的先後順序則沒什麼要求!(位置參數跟位置參數之間的先後順序?腦子進水了纔會想這個問題)

所有擴展位置參數一股腦塞進一個PyTupleObject,該PyTupleObject對象被Python虛擬機通過SETLOCAL放到了PyFrameObject對象的f_localsplus中,位置是co->co_argcount,普通位置參數列表後的第一個位置。

當函數擁有擴展位置參數時,擴展鍵參數的PyDictObject對象在f_localsplus中的位置一定在擴展位置參數PyTupleObject對象之後的下一個位置。


函數內部的局部變量和函數參數一樣,都是存在f_localsplus中運行時棧前面的那段內存中間中。(爲什麼在函數實現中沒有local名字空間呢?因爲函數中局部變量總是固定不變的,所以在編譯時就能確定局部變量使用的內存空間的位置,也能確定訪問局部變量的字節碼指令應該如何訪問內存。跟查名字空間相比這種靜態訪問更快。)


[閉包]

名字空間和函數捆綁後的結果被稱爲閉包(closure)。閉包是Python的核心作用域規則(最內嵌套作用域規則)的一種實現形式。

closure相關:

PyCodeObject:

PyCodeObject.co_cellvars 通常是一個tuple,保存嵌套的作用域中使用的變量名集合

PyCodeObject.co_freevars 通常是一個tuple,保存使用了的外層作用域中的變量名集合

PyFrameObject:

PyFrameObject.f_localsplus 指向的內存包含:局部變量(參數+局部變量)、cell對象(PyCodeObject.co_cellvars對應的PyCellObject)、free對象(PyCodeObject.co_freevars對應的PyCellObject)、運行時棧

PyFunctionObject:

PyFunctionObject.func_closure NULL or a tuple of PyCellObject

PyCellObject,cell對象在PyFrameObject.f_localsplus中局部變量之後的位置。對cell對象的訪問也是通過基於索引訪問PyFrameObject.f_localsplus來完成,不需要知道cell變量對應的變量名。在CALL_FUNCTION指令執行過程中(PyEval_EvalCodeEx()函數內部)會創建新的PyFrameObject對象,併爲其創建ob_ref=NULL的PyCellObject對象,並拷貝到PyFrameObject.f_localsplus中,在執行到相關變量(被內嵌函數引用的局部變量)的賦值語句時纔會設置PyCellObject.ob_ref(STORE_DEREF指令)。

內嵌函數的定義語句“def inner_func():”編譯成字節碼“LOAD_CLOSURE,BUILD_TUPLUE,LOAD_CONST,MAKE_CLOSURE,STORE_FAST”,這些指令執行時創建內嵌函數對應的PyFunctionObject對象,並將[內嵌函數對應的PyCodeObject]/[global名字空間]/[PyCellObject的tuple(約束集合)]/[默認參數]都設置到PyFunctionObject內部成員身上,然後PyFunctionObject對象被放入到當前函數棧幀(內嵌函數外部這個函數)的f_localsplus中(局部變量區域)。

內嵌函數執行時(CALL_FUNCTION指令),PyEval_EvalCodeEx()函數根據PyFunctionObject(內嵌函數對象).PyCodeObject.co_freevars(引用的外部作用域中的符號名)把參數closure(傳入的外部函數構建的閉包tuple)中的PyCellObject對象一個一個放入到PyFrameObject(當前棧幀對象,內嵌函數對應的棧幀).f_localsplus相應位置。

裝飾器(Decorator)僅僅是對原函數func做"func=decorator_func(func)"包裝。用的還是closure的概念。


[第十二章]Python虛擬機中的類機制

12.1

Python2.2之前用戶定義的class不能繼承自內置的type(int,dict等)。

Python2.2之前Python中實際存在三種對象:

type對象表示Python內置的類型

class對象表示Python程序員定義的類型

instance對象(實例對象)表示由class對象創建的實例

Python2.2之後type和class已統一。我們用<class A>表示名爲A的class對象;<instance a>表示名爲a的instance對象;術語type表示“類型”(注意,不是類型對象)這個概念;術語class表示“類”或“類型”這個概念,而“class對象”表示這個概念在Python中的實現。

通過對象的__class__屬性或Python內置的type方法可以探測一個對象和哪個對象存在is-instace-of關係;通過對象的__bases__屬性可以探測一個對象和哪個對象存在is-kind-of關係;Python還提供了兩個內置方法issubclass和isinstanceof來判斷兩個對象間是否存在我們期望的關係。

[dongsong@localhost python_study]$ cat metaclass.py 
#encoding=utf-8


class A(object):
        pass


if __name__ == '__main__':
        a = A()
        print 'a.__class__ = %s' % str(a.__class__)
        print 'type(a) = %s ' % str(type(a))
        print 'A.__class__ = %s' % str(A.__class__)
        print 'type(A) = %s' % str(type(A))
        print 'object.__class__ = %s' % str(object.__class__)
        print 'type(object) = %s' % str(type(object))
        print 'A.__bases__ = %s' % str(A.__bases__)
        print 'object.__bases__ = %s' % str(object.__bases__)
        try:
                print 'a.__bases__ = %s' % str(a.__bases__)
        except Exception,e:
                print '%s: %s' % (type(e), str(e))
        print 'isinstance(a,A) = %s' % str(isinstance(a,A))
        print 'issubclass(A,object) = %s' % str(issubclass(A,object))
        print 'type.__class__ = %s' % str(type.__class__)
        print 'type.__bases__ = %s' % str(type.__bases__)
        print 'int.__class__ = %s' % str(int.__class__)
        print 'int.__bases__ = %s' % str(int.__bases__)
        print 'dict.__class__ = %s' % str(dict.__class__)
        print 'dict.__bases__ = %s' % str(dict.__bases__)
[dongsong@localhost python_study]$ vpython metaclass.py 
a.__class__ = <class '__main__.A'>
type(a) = <class '__main__.A'> 
A.__class__ = <type 'type'>
type(A) = <type 'type'>
object.__class__ = <type 'type'>
type(object) = <type 'type'>
A.__bases__ = (<type 'object'>,)
object.__bases__ = ()
<type 'exceptions.AttributeError'>: 'A' object has no attribute '__bases__'
isinstance(a,A) = True
issubclass(A,object) = True
type.__class__ = <type 'type'>
type.__bases__ = (<type 'object'>,)
int.__class__ = <type 'type'>
int.__bases__ = (<type 'object'>,)
dict.__class__ = <type 'type'>
dict.__bases__ = (<type 'object'>,)

<type 'type'>被成爲metaclass對象(元類對象),屬於Python中的一種特殊的class對象,這種特殊的class對象能成爲其他class對象的type。

<type 'object'>萬物之母,任何一個class都必須直接或間接繼承自object。[圖12-3]


總結:

在Python中,任何一個對象都有一個type,可以通過對象的__class__屬性獲得。任何一個instance對象的type都是一個class對象,而任何一個class對象的type都是metaclass對象。在大多數情況下這個metaclass都是<type 'type'>,而在Python內部,它實際上對應的就是PyType_Type。

在Python中,任何一個class對象都直接或間接與<type 'object'>對象之間存在is-kind-of關係,包括<type 'type'>。在Python內部,<type 'object'>對應的是PyBaseObject_Type。


12.2

[圖12-4]


只要一個對象對應的class對象中實現了"__call__"操作(在Python內部的PyTypeObject中,tp_call不爲空),那麼該對象就是一個可調用(callable)的對象。在Python內部,是通過一個名爲PyObject_Call的函數對instance對象進行操作,從而調用對象中的__call__,完成"可調用"特性的。一個對象是否可調用並不是在編譯期能確定的,必須是在運行時才能在PyObject_CallFunctionObjArgs中確定。

從Python2.2開始,Python啓動時會對類型系統(對象模型)進行初始化動作,動態的在內置類型對應的PyTypeObject(比如,對於int就是修改全局變量PyInt_Type的成員屬性)中填充一些重要的東西(包括填充tp_dict,從而完成內置類型從type對象到class對象的轉變)。這個初始化動作從_Py_ReadyTypes函數開始(內部調用PyType_Ready函數)。

class對象(如PyInt_Type)的ob_type信息(如PyType_Type)就是對象的__class__將返回的信息;ob_type就是metaclass。Python虛擬機將基類(ob_base)的metaclass作爲了子類的metaclass。[表12-1]


PyHeapTypeObject定義中的各個域的順序隱含着操作優先級信息。比如,PyMappingMethods的位置在PySequenceMethods之前,mp_subscript是PyMappingMethods中的PyObjet*,而sq_item是PySequenceMethods中的PyObject*,所以最終計算出的偏移:offset(mp_subscript)<offset(sq_item)。PyList_Type這個PyTypeObject中既定義了mp_subscript又定義了sq_item(PyList_Type.tp_as_mapping.mp_subscript的值爲list_subscript,PyList_Type.tp_as_seqence.sq_item的值爲list_item),那麼Python虛擬機會選擇mp_subscript與"__getitem__"建立聯繫。

vi -t PyHeapTypeObject
/* The *real* layout of a type object when allocated on the heap */
typedef struct _heaptypeobject {
    /* Note: there's a dependency on the order of these members
       in slotptr() in typeobject.c . */
    PyTypeObject ht_type;
    PyNumberMethods as_number;
    PyMappingMethods as_mapping;
    PySequenceMethods as_sequence; /* as_sequence comes after as_mapping,
                                      so that the mapping wins when both
                                      the mapping and the sequence define
                                      a given operator (e.g. __getitem__).
                                      see add_operators() in typeobject.c . */
    PyBufferProcs as_buffer;
    PyObject *ht_name, *ht_slots;
    /* here are optional user slots, followed by the members. */
} PyHeapTypeObject;

整個對slotdefs排序在init_slotdefs函數中完成。slot中的offset正是操作排序的關鍵所在。

slot可以視爲PyTypeObject中定義的操作,包含操作名name、操作的函數地址在PyHeapTypeObject中的偏移量offset、函數指針等。

PyTypeObject對象的tp_dict成員是一個PyDictObject對象,key是"__getitem__"等操作名,value是包裝了slot的PyObject(被稱爲descriptor)。

typedef struct wrapperbase slotdef;
struct wrapperbase {
    char *name;
    int offset;
    void *function;
    wrapperfunc wrapper;
    char *doc;
    int flags;
    PyObject *name_strobj;
};

[第十三章]Python運行環境初始化
Py_Main()-->Py_Initialize(..)-->Py_InitializeEx(..)
PyInterpreterState結構體是對進程的模擬,PyThreadState結構體是對線程的模擬(第八章有提到)。
圖13-1.Python運行時的整個環境


初始化線程環境

Py_InitializeEx()-->PyInterpreterState_New()創建PyInterpreterState對象;PyThreadState_New()創建PyThreadState對象。

interp_head是PyInterpreterState對象通過next指針形成鏈表結構的表頭,模擬OS多進程。

PyThreadState對象通過next指針形成鏈表模擬OS多線程。

PyFrameObject對象鏈表就是函數調用棧。

圖13-3.PyInterpreterState和PyThreadState之間的聯繫


全局變量_PyThreadState_Current維護者當前活動線程PyThreadState對象。Python啓動時創建第一個PyThreadState對象後用PyThreadState_Swap()函數設置這個全局變量。

Py_InitializeEX()-->_Py_ReadyTypes()初始化Python類型系統(Python的類機制裏面有描述)。

Py_InitializeEX()-->_PyFrame_Init()設置全局變量builtin_object.

__builtin__ module

Python創建的第一個module是__builtin__ module.在Py_InitializeEx()-->_PyBuiltin_Init()中實現。

PyObject* Py_InitModule4(const char *name, PyMethodDef *methods, const char *doc, PyObject *passthrough, int module_api_version)

name: module的名稱

methods: 該module中所包含的函數的集合

doc: module的文檔

passthrough: 在Python2.5中沒用,NULL

module_api_version: Python內部使用的version值

該函數分兩部分:

一個是創建module對象(PyImport_AddModule()),先從module pool(Python內部維護着所有加載到內存的module的dict,即interp->modules,也即sys.modules)中查看是否存在同名的module,存在則直接返回,不存在就通過PyModule_New()創建新的module對象並將(name,module)對應關係插入到module集合.

另一個是將(符號,值)對應關係放置到創建的module中。在創建__builtin__ module時,這裏做的工作是:對builtin_methods中每個PyMethodDef結構,PyInitModule4()都會基於它來創建一個PyCFunctionObject對象(參加PyCFunction_NewEx函數),這個對象是Python中對函數指針(和其他信息)的包裝。(PyCFunctionObject.m_module指向的是PyStringObject對象,該對象維護的是某個PyModuleObject對象的名字)

圖13-5.__builtin__ module示意圖


interp->builtins指向__builtin__ module維護的PyDictObject對象,省去去interp->modules中查找。

sys module

interp->sysdict指向sys module維護的PyDictObject對象。

圖13-6.完成sys module創建後的內存佈局


interp->modules是一個PyDictObject對象,存儲(module name, PyModuleObject),對於Python的標準擴展module,爲防止從該dict刪除後的再次初始化,Python將所有擴展module用一個全局PyDictObject對象進行備份維護(_PyImport_FixupExtension())。

sys.path是Python搜索一個module時的默認搜索路徑集合(PySys_SetPath()).

初始化import機制的環境 _PyImport_Init()
初始化Python內建exceptions _PyExc_Init()
備份exceptions module和__builtin__ module _PyImport_FixupExtension("exceptions", "exceptions"),_PyImport_FixupExtension("__builtin__","__builtin__")
在sys.module中添加一些對象用於import機制 _PyImportHooks_Init()
__main__ module

Py_InitializeEx()-->initmain():

創建__main__ module,插入interp->modules

獲取__main__ module中的dict

獲取interp->modules中的__builtin__ module

將("__builtins__", __builtin__ module)插入__main__ module的dict中(WARNING: __builtin__ module的__name__是"__builtin__",而在__main__ module名字空間中的符號是"__builtins__")

__main__ module的__name__是"__main__"

作爲主程序運行的Python源文件可以被視爲名爲__main__的module; 以import方式加載的py文件其__name__不會是"__main__"

Python交互模式下,dir()輸出的結果正是__main__ module的內容

site-specific module的搜索路徑

規模較大的第三方庫都會放在%PythonHome%/lib/site-packages目錄下,%PythonHome%/lib目錄下的標準庫site.py把這些第三方庫加入Python的搜索路徑(sys.path)中

Py_InitializeEx()-->initsite()負責加載site.py,site.py模塊的工作是:

1.將site-packages路徑加入到sys.path中

2.講site-packages目錄下的所有.pth文件中的所有路徑加入到sys.path中

圖13-9.完成初始化之後的環境


激活Python虛擬機

Py_Main()-->int PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, PyCompilerFlags *flags)

以腳本文件方式運行Python時filename就是文件名;以爲交互方式運行Python時filename爲NULL。

PyRun_AnyFileExFlags()內部,兩種方式分道揚鑣最後殊途同歸一起進入了run_mod()函數

PyRun_AnyFileExFlags()-->PyRun_InteractiveLoopFlags() 交互模式運行

PyRun_InteractiveLoopFlags()-->PyRun_InteractiveOneFlags()

PyParser_ASTFromFile()解析和編譯輸入的Python語句,構建AST(抽象語法樹)

獲取__main__ module維護的PyDictObject

調用run_mod()執行用戶輸入的語句(__main__ module維護的PyDictObject作爲參數傳入run_mod(),其作爲虛擬機開始執行時當前活動frame對象的local和global名字空間)

PyRun_AnyFileExFlags()-->PyRun_SimpleFileExFlags() 腳本文件方式運行

設置__main__ module的__file__屬性

PyRun_FileExFlags()執行腳本文件

PyParser_ASTFromFile()解析和編譯輸入的Python語句,構建AST(抽象語法樹)

run_mod()做執行(傳入run_mod()的local和global名字空間也是__main__ module維護的PyDictObject)

static PyObject *run_mod(mod_ty mod, const char *filename, PyObject *globals, PyObject *locals, PyCompilerFlags *flags, PyArena *arena)

PyAST_Compile()基於AST編譯出字節碼指令序列,創建PyCodeObject對象

PyEval_EvalCode()創建PyFrameObject對象,執行PyCodeObject對象中的字節碼指令序列:PyEval_EvalCode()-->PyEval_EvalCodeEx()

PyThreadState_GET()獲取線程對象

PyFrame_New()創建PyFrameObject對象

PyEval_EvalFrameEx()執行字節碼,大循環

PyFrameObject *PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyObject *locals)

設置builtin名字空間(Python所有線程共享同樣的builtin名字空間)

設置global名字空間

設置local名字空間:調用函數時不用創建local名字空間;一般情況下locals和globals指向相同的dict

[第十四章]Python模塊的動態加載機制
【黑盒測試】
dir():沒有參數時打印出當前local名字空間的所有符號;有參數時將參數視爲對象,輸出該對象的所有屬性。
sys.modules

全局的module集合,作爲module pool,保存了module的唯一映像,當某個.py文件通過import聲明感知某個module(以某個符號的形式引入到某個名字空間)時,Python虛擬機會在這個pool中查找,如果已經存在就引入一個符號到該.py文件的名字空間,並將其關聯到該module,使其透過和這個符號被.py文件感知到;如果該module不在pool中,這時Python纔會執行動態加載的動作。如果要重新加載需要用builtin module中的reload操作來實現(reload背後沒有創建新的module對象,這是對原有module做更新,故module的id不變)。

import

import的真實含義是將某個module以某個符號的形式引入到某個名字空間。

對於內建module:Python環境初始化時加載的sys等module沒有暴露在當前local名字空間,需要用戶顯式用import引入到local名字空間,這些預先加載的module存放在sys.modules中。

對於用戶自定義module:import會創建一個新的module並引入到當前local名字空間中,而且這個被動態加載的module也會加入到sys.modules(實際上,所有的import動作,不論發生在什麼時間什麼地方都會影響到全局module集合;這樣做有一個好處,即如果程序的另一點再次import這個module,Python虛擬機只需要將全局module集合中緩存的那個module對象返回即可)。

package

module對象內部通過一個dict維護所有(屬性,屬性值).同class一樣,module又是一個名字空間。

module管理class和函數,package管理module,多個較小的package又可以聚合成一個較大的package. module由一個單獨的文件來實現(.py .pyc .dll),package由文件夾來實現。

只有文件夾中有__init__.py文件時,Python虛擬機纔會認爲這個文件夾是合法的package.

import A.tank : 會導致當前local名字空間增加一個"A"符號代表的module對象(其實是一個package),sys.module(這個dict)中增加兩個分別以“A”和“A.tank”爲key、以對應module爲value的元素。

import A.B.c:會導致當前local名字空間增加一個"A"符號,sys.modules中增加"A"、"A.B"、"A.B.c"; 具體過程是,分解成(A,B,c)的節點集合,從左到右依次去sys.modules中查找每個符號對應的module(A,A.B,A.B.c),如果只找到A(可能由之前的那個import A.tank引入的),就取出A對應的PyModuleObject對象中的元信息__path__(代表當前package的路徑),接下來對B的搜索將只在A.__path__中進行,而不是在Python的所有搜索路徑中執行。

from import

使得Python虛擬機在當前名字空間中引入的符號儘可能的少,更好的避免名字空間遭到污染。
from A import tank: 當前local名字空間中增加"tank",sys.modules中增加"A"和"A.tank"。故本質上跟import A.tank一樣,都是講packet A和module A.tank動態加載到sys.modules結合中,區別在於Python會在當前local名字空間中引入什麼符號。
from A.tank import a: 僅僅將module A.tank中的對象a暴露到當前local名字空間中,當前locals()中會增加一個"a",sys.modules中會增加"A"和"A.tank"(a只是module A.tank的一個對象,故sys.modules中不會有A.tank.a這樣的module存在).
import A.tank as Tank
locals()中增加一個符號"Tank",映射module A.tank;sys.modules中增加module A和module A.tank.

as可以控制module以什麼名字被引入到當前的locals().


【內部探測(白盒測試)】
import包含三項功能:

Python運行時的全局module pool的維護和搜索

解析和搜索module路徑的樹狀結構

對不同文件格式的module的動態加載機制

當Python虛擬機import一個package或module時,都會創建一個module對象,並且設置其__name__和__path__,只不過module對應的__path__爲空罷了。
我門將所有import動作都歸一到同一個抽象原則下:Python的import動作都發生在某一個package的環境裏。
reload(xx) 不會把源文件中刪除的符號從內存的module中刪除,只會更新或添加新的符號,參見load_module(..)-->load_source_module(..)-->PyImport_ExecCodeModuleEx(..)
Python虛擬機初始化過程中在內部維護了一個對於內建module的備份,所有的內建module,一旦被加載之後都會拷貝一份,存到這個備份中,以後再次import時就可以不用再進行module的初始化動作,直接使用備份中緩存的module就可以了。Python對於擴展module,也做了備份。
imp module: .py源碼文件中的import語句是靜態的,Python也提供了動態的import機制,即在運行時動態的選擇需要import的對象。imp module暴露了用於import機制的核心接口。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章