Python解釋器源碼分析(二):print "Hello World"

目錄

0x01 準備工作

0x02 運行輸出

0x03 主要流程分析

1 初始化

1.1 數據類型準備 

1.2 內置對象初始化

2 運行

2.1 申請內存池

2.2 詞法及語法解析

2.3 解析樹節點類型

2.4 運行

0x04 總結


本節以交互模式下執行print “Hello World”爲例分析解釋器的執行流程。

0x01 準備工作

  • 打開Python-2.7.9\PCbuild目錄下的visual studio解決方案pcbuild
  • 設置python工程爲啓動工程
  • 打開詞法語法分析調試變量,python\pythonrun.c源碼中修改int Py_DebugFlag = 1
  • 編譯python工程

0x02 運行輸出

    以Debug模式啓動python工程,出現命令行提示窗口,輸入print “Hello World”回車運行結果如下圖所示。

0x03 主要流程分析

   在解釋器命令行窗口輸入print “Hello World”回車後執行流程是怎樣的呢? Python源碼工程支持visual studio的編譯和調試,加上visual studio非常強大的單步調試能力,因此對執行流程的分析基本沒什麼技術難度。這裏簡單總結下大致的運行流程,有興趣的童鞋可以自己運行看看。

1 初始化

   上一節中提到過Py_Main函數,它是python進程的入口函數,主要包括參數解析、初始化、運行等邏輯。初始化工作主要完成解釋器的初始化,由Py_Main函數調用Py_Initialize函數完成,Py_Initialize函數位於Python\pythonrun.c源碼文件內,初始化完成的主要流程如下圖所示:

1.1 數據類型準備 

    這裏先看下數據類型準備_Py_ReadyTypes,類型的準備主要完成各種數據類型的準備工作。Python C源碼中,所有內置的數據類型都是PyTypeObject結構體的一個實例,PyTypeObject結構體中以函數指針的形式,聲明瞭各種處理函數,包括print、repr、hash、call、str、getattro、成員函數(tp_methods)、成員變量(tp_members)等。各個內置數據類型的定義在Objects目錄下相應的C源碼文件中,比如list數據類型實現的源碼文件是Objects\listobject.c,在listobject.c源碼文件中定義了結構體實例PyList_Type對象,如下圖所示。

    _Py_ReadyTypes函數主要調用Objects\typeobject.c\PyType_Ready函數對內置的數據類型的結構體對象的個元素進行初始化。

    數據類型的初始化,主要完成對int、long、bytearray、float等數據類型的某些特定參數進行初始化。這裏有一個東西沒有搞明白,bytearray的初始化C函數,PyByteArray_Init函數什麼事都沒幹直接返回1。

1.2 內置對象初始化

  內置對象初始化,也就是__builtin__模塊的初始化,通過調用Python\bltinmodule.c中的_PyBuiltin_Init函數完成內置常量、內置數據類型和函數的初始化。內置模塊初始化包括兩個動作,一是調用PyDict_SetItemString設置__builtin__模塊的__dict__屬性,二是調用Objects\object.c\_Py_AddToAllObjects函數將內置對象的結構體實例添加到雙向循環鏈表refchain。代碼如下圖所示:

    內置常量主要包括None、Ellipsis、NotImplemented、False、True。None、Ellipsis、NotImplemented在內存中的對象是PyObject結構體(在Include\object.h中定義)實例,True和False是PyIntObject結構體(在Include\intobject.h中定義)實例。

    內置數據類型和函數,和內置常量的初始化動作一樣。這裏提一下,包括property、super、object、type、classmethod、staticmethod、str、file等內置對象都是PyTypeObject結構體(在Include\object.h中定義)實例。

2 運行

 初始化完成後,進入交互模式。主要流程如下圖。

2.1 申請內存池

    Python採用內存池的方式管理內存的使用,實現源碼爲Pyhon\pyarena.c。其主要思想是先在內存中申請一塊內存,後續在該預先申請的內存塊上分配相應的空間給對象使用。arena內存池的管理由兩個結構體實現,_block和_arena。內存池管理機制後續進行分析。

2.2 詞法及語法解析

    詞法及語法解析,主要調用Parse\parsetok.c\parsetok函數。該函數主要流程如下圖所示。

    在交互命令行輸入的print “Hello World”的單詞解析信息存儲在結構體parse_state結構體(在Parse\parser.h中定義)

   

    該結構體中有一顆解析樹,該解析樹存儲了詞法語法解析結果,各節點是_node類型的結構體。parsetok函數最終返回該解析樹的top節點。值得注意的是,新解析出來的單詞會作爲最新的top節點,並且如果設置了編碼方式,解析樹的top節點是編碼方式。

   使用Parser\parser.c中的dumptree、showtree、printtree調試函數可以打印解析樹。利用dumptree函數對parseok返回的解析樹輸出如下。

    詞法語法解析完畢後,調用Parse\ast.c\PyAST_FromNode函數,將解析樹各個節點由CST爲AST(抽象語法樹,Abstract Syntax Tree)。CST和AST都是語法分析的中間結果,不同的地方是CST直接對應語法分析的匹配過程,含有大量的冗餘信息,AST省略了大量冗餘信息,直接對應實際的語義,也就是最終的分析結果。

    Python爲我們提供了parse和ast模塊,將其內部的單詞和語法解析、字節編譯暴露了出來。利用parse和ast模塊,我們可以訪問python解析樹。在python解釋器命令行中導入模塊parser,然後調用parser. suite(‘print “Hello World”’),可以看到解析結果和c函數dumptree的輸出是一樣的。同樣使用ast模塊的parse和dump函數可以看到ast解析結果。

import parser
cst = parser.suite('print "Hello World"')
print cst.tolist()
# [257, [267, [268, [269, [272, [1, 'print'], [304, [305, [306, [307, [308, [310, [311, [312, [313, [314, [315, [316, [317, [318, [3, '"Hello World"']]]]]]]]]]]]]]]]], [4, '']]], [4, ''], [0, '']]

import ast
astrst = ast.parse('print "Hello World"')
print ast.dump(astrst)
# Module(body=[Print(dest=None, values=[Str(s='Hello World')], nl=True)])

2.3 解析樹節點類型

         如2.2中描述,詞法與語法分析的結果是一顆詞法語法解析樹,該樹的各節點的結構體定義(在Include\node.h中)如下:

     那麼節點類型有哪些呢?類型的定義是在Include\token.h及graminit.h兩個頭文件中給出了定義。例如257爲文件輸入、267爲語句、268爲單個語句、272爲print語句、1爲名稱、310爲表達式、312爲AND表達式、3爲字符串、4爲新行、0爲結束標誌。

2.4 運行

    運行主要調用Python\pythonrun.c\run_mod函數,其主要包括兩個步驟,編譯和執行。編譯調用python\compile.c中的PyAST_Compile函數,對ast解析結果結構體mod_ty(在Include\Python-ast.h中定義)對象進行編譯,返回代碼結構體PyCodeObject結構體(在Include\code.h中定義)對象,編譯執行的具體實現細節後面再進行分析。另外,上述過程中的所有結中數據包括詞法語法解析樹、AST解析結果、編譯等結果都存儲在2.1中申請的內存池中。

0x04 總結

    本節簡單分析了交互模式下print “Hello World”語句的執行流程,初步掌握了python解釋器的工作流程。接下來會對內部一些核心的機制進行分析,包括arena內存池、單詞解析、語法分析、編譯、執行、全局鎖等。

更多文章敬請關注

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