成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Python源碼漫游指南(一)

dackel / 877人閱讀

摘要:源碼漫游指南一作者秘塔科技算法研究員前幾天發(fā)布了第五屆頂級語言交互排行榜,語言繼續(xù)穩(wěn)坐第一把交椅,并且相比去年的排行情況,拉開了與第二名的距離去年第二名的排名得分為。包含字節(jié)碼相關(guān)的底層抽象。字節(jié)碼對象的實現(xiàn)。源文件執(zhí)行結(jié)束后的清理工作。

Python源碼漫游指南(一)

作者:秘塔科技算法研究員 Qian Wan

前幾天IEEE Spectrum發(fā)布了第五屆頂級語言交互排行榜,Python語言繼續(xù)穩(wěn)坐第一把交椅,并且相比去年的排行情況,拉開了與第二名的距離(去年第二名的排名得分為99.7)。從下圖能看出Python的優(yōu)勢還是很明顯的,而且在Web、企業(yè)級和嵌入式這三種應(yīng)用類別的流行度都很高。

冰凍三尺非一日之寒。Python語言自1990年由Guido van Rossum第一次發(fā)布至今已經(jīng)快三十年的歷史,它支持多種操作系統(tǒng),并以CPython為參考實現(xiàn)。Python語言在很多領(lǐng)域都有殺手級的應(yīng)用框架,如深度學(xué)習(xí)方面有PyTorch和Tensorflow,自然語言處理有NLTK,Web框架有Django、Flask,科學(xué)計算有Numpy、Scipy,計算機視覺有OpenCV,科學(xué)繪圖有Matplotlib,爬蟲有Scrapy,凡此種種,不一而足。面對這么多不同種類的Python應(yīng)用框架,下面一些問題是值得我們思考的:

怎樣使用Python語言能將程序的性能發(fā)揮到極致?

什么類型的單一語言框架不適合用Python來實現(xiàn)?

多語言框架中與Python語言的交互如何做到高效?

從架構(gòu)的角度看,Python內(nèi)部的架構(gòu)設(shè)計如何?

從使用Python語言的角度,它適合于什么樣的軟件架構(gòu)設(shè)計?

在多語言(Python與CUDA)、異構(gòu)節(jié)點(CPU與GPU)、多業(yè)務(wù)類型(IO密集型與CPU密集型)以及跨區(qū)域(跨國多機房)的復(fù)雜系統(tǒng)中,Python語言的定位又如何?其他語言呢?

三言兩語可能很難比較全面的回答上面一些問題,而且只研究Python語言得到的答案也可能會有失偏頗。但是Python語言的源代碼能夠為回答這些問題提供一些線索,而且通過閱讀源碼能讓我們在使用Python語言時看到一些以前我們看不到的細節(jié),就如同《黑客帝國》電影里的Neo一樣能看到母體世界的源代碼,也能像Neo那樣在機器的世界里飛天遁地。

Python環(huán)境的部署

我們使用pyenv花幾分鐘時間來構(gòu)建Python運行環(huán)境,它不僅可以與操作系統(tǒng)原生的Python環(huán)境隔離,還能支持多種版本的Python環(huán)境,另外也支持在同一Python版本下的多個虛擬環(huán)境,可以用來隔離不同應(yīng)用的Python依賴包。部署代碼如下

$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ echo "export PYENV_ROOT="$HOME/.pyenv"" >> ~/.bashrc
$ echo "export PATH="$PYENV_ROOT/bin:$PATH"" >> ~/.bashrc
$ git clone https://github.com/pyenv/pyenv-virtualenv.git ${HOME}/.pyenv/plugins/pyenv-virtualenv
$ echo "eval "$(pyenv init -)"" >> ~/.bashrc
$ echo "eval "$(pyenv virtualenv-init -)"" >> ~/.bashrc
$ CONFIGURE_OPTS=--enable-shared $HOME/.pyenv/bin/pyenv install 3.6.6 -k -v
$ $HOME/.pyenv/bin/pyenv virtualenv 3.6.6 py3.6

部署好了之后每次運行下面命令就能替換掉系統(tǒng)原生的Python環(huán)境

$ pyenv activate py3.6

安裝后的目錄結(jié)構(gòu)如下

Python源碼:~/.pyenv/sources/3.6.6/Python-3.6.6

頭文件:~/.pyenv/versions/3.6.6/include/python3.6m/

動態(tài)鏈接庫:~/.pyenv/versions/3.6.6/lib/libpython3.6m.dylib

目錄結(jié)構(gòu)

要深入剖析Python的源代碼,就要對源碼中幾個大的模塊的作用有一個初步的認識。我們進入到源碼目錄~/.pyenv/sources/3.6.6/Python-3.6.6,其中幾個跟Python語言直接相關(guān)的目錄及其功能如下

Include:C頭文件,與部署好的頭文件目錄~/.pyenv/versions/3.6.6/include/python3.6m/中的文件一致(嚴格來說,部署好的頭文件目錄中會多一個自動生成的pyconfig.h文件),這些頭文件定義了Python語言的底層抽象結(jié)構(gòu)。

Lib:Python語言庫,這部分不參與Python的編譯,而是用Python語言寫好的模塊庫。

Modules:用C語言實現(xiàn)的Python內(nèi)置庫。

Objects:Python內(nèi)置對象的C語言實現(xiàn)以及抽象接口的實現(xiàn)。

Parser:Python編譯器的前端,詞法分析器語法分析器。后者就是基于龍書的LL(1)實現(xiàn)的。

Programs:可執(zhí)行文件~/.pyenv/versions/3.6.6/bin/python的源碼所在的目錄。

Python:Python虛擬機所在的目錄,也是整個Python語言較為核心的部分。

使用下面的圖示能更好的展示這些目錄之前的相互關(guān)系,虛線箭頭表示提供接口定義,實線箭頭表示提供服務(wù),自頂向下的結(jié)構(gòu)也體現(xiàn)了語言設(shè)計在架構(gòu)上的層次關(guān)系。

Include目錄

從上面這些模塊的大致功能上分析,我們可以判斷出Include、ObjectsPython中的代碼比較重要。我們先看一下這三個目錄包含的代碼量

$ cat Include/* Objects/* Python/* | wc -l
cat: Objects/clinic: Is a directory
cat: Objects/stringlib: Is a directory
cat: Python/clinic: Is a directory
  215478

21萬行代碼的閱讀量有點略大,我們還是先挨個看看這些目錄中文件的命名、大小以及一些注釋,看能不能得到一些線索。

$ wc -l Include/*.h | sort -k1
     ...
     324 pystate.h
     370 objimpl.h
     499 dynamic_annotations.h
     503 pyerrors.h
     637 Python-ast.h
     767 pyport.h
    1077 object.h
    1377 abstract.h
    2342 unicodeobject.h
   15980 total

從文件名和文件大小可以初步判斷object.habstract.h是兩個比較重要的頭文件,實際上它們定義了Python底層的抽象對象以及統(tǒng)一的抽象接口。
unicodeobject.h雖然體積大,但是有很多跟它類似的頭文件,如boolobject.hlongobject.h、floatobject.h等等,這些頭文件應(yīng)該是內(nèi)置類型的頭文件,我們可以暫時不去理會這些文件,對語言的總體理解不會造成困難。

為了不漏掉一些重要的頭文件,我們快速閱讀一下其他頭文件中可能包含的一些引導(dǎo)性的注釋,發(fā)現(xiàn)這些頭文件也比較重要:

Python.h:元頭文件,通常在寫Python的C擴展時會包含它。

ceval.h:作為Python/ceval.c的頭文件,而Python/ceval.c負責(zé)運行編譯后的代碼。

code.h:包含字節(jié)碼相關(guān)的底層抽象。

compile.h抽象語法樹的編譯接口。

objimpl.h:跟內(nèi)存相關(guān)的抽象對象高層接口,如內(nèi)存分配,初始化,垃圾回收等等。

pystate.h線程狀態(tài)解釋器狀態(tài)以及它們的接口。

pythonrun.h:Python代碼的語法分析與執(zhí)行接口。

通過以上篩選,我們看看還剩下多少代碼:

$ cat object.h abstract.h objimpl.h Python.h ceval.h code.h compile.h pystate.h pythonrun.h | wc -l
    3950

核心頭文件壓縮到不到4千行。

Objects目錄

用類似的思路,我們能從Objects目錄中篩選出一些比較重要的文件

abstract.c抽象對象的接口實現(xiàn)。

codeobject.c:字節(jié)碼對象的實現(xiàn)。

object.c:通用對象操作的實現(xiàn)。

obmalloc.c:內(nèi)存分配相關(guān)實現(xiàn)。

typeobject.cType對象實現(xiàn)。

統(tǒng)計一下代碼量

$ wc -l abstract.c codeobject.c object.c obmalloc.c typeobject.c
    3246 abstract.c
     921 codeobject.c
    2048 object.c
    2376 obmalloc.c
    7612 typeobject.c
   16203 total

一下子新增了1.6萬行,畢竟是實打?qū)嵉腃語言實現(xiàn)。

另外還有一些具象化的對象實現(xiàn)文件,雖然它們跟longobject.cdictobject.c之類的對象實現(xiàn)類似,都是具體的對象,但是它們跟Python語言特性比較相關(guān),在這里也把它們列出來,做為備份。

classobject.c:類對象實現(xiàn)。

codeobject.c:代碼對象實現(xiàn)。

frameobject.c:Frame對象實現(xiàn)。

funcobject.c:函數(shù)對象實現(xiàn)。

methodobject.c:方法對象實現(xiàn)。

moduleobject.c:模塊對象實現(xiàn)。

順便統(tǒng)計下行數(shù)

$ wc -l classobject.c codeobject.c frameobject.c funcobject.c methodobject.c moduleobject.c
     648 classobject.c
     921 codeobject.c
    1038 frameobject.c
    1031 funcobject.c
     553 methodobject.c
     802 moduleobject.c
    4993 total

Objects目錄中合計約2.1萬行。通過探索這些源代碼,我們看出Python的一個設(shè)計原則就是:一切皆對象。

嚴格來說,只有Python語言暴露給外部使用的部分才抽象成了對象,而一些僅在內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu)則沒有對象封裝,如后面會提到的解釋器狀態(tài)線程狀態(tài)等。
Python目錄

依然經(jīng)過一輪篩選,能得到下面這些比較重要的文件

ast.c:將具體語法樹轉(zhuǎn)換成抽象語法樹,主要函數(shù)是PyAST_FromNode()

ceval.c:執(zhí)行編譯后的字節(jié)碼。

ceval_gil.h全局解釋器鎖(Global Interpreter Lock,GIL)的接口。

compile.c:將抽象語法樹編譯成Python字節(jié)碼。

pylifecycle.c:Python解釋器的頂層代碼,包括解釋器的初始化以及退出。

pystate.c線程狀態(tài)解釋器狀態(tài),以及它們的接口實現(xiàn)。

pythonrun.c:Python解釋器的頂層代碼,包括解釋器的初始化以及退出。

能夠注意到,pylifecycle.cpythonrun.c的功能是類似的,實際上查閱Python開發(fā)歷史記錄能發(fā)現(xiàn)前者是因為開發(fā)需要從后者分離出來的。統(tǒng)計一下代碼的數(shù)量:

$ wc -l ast.c ceval.c ceval_gil.h compile.c pystate.c pythonrun.c
    5277 ast.c
    5600 ceval.c
     270 ceval_gil.h
    5329 compile.c
     958 pystate.c
    1596 pythonrun.c
   19030 total

這樣濃縮下來IncludeObjectsPython三個文件夾中比較重要的代碼一共大約4.4萬行,先不說我們這樣篩選出來的一波有沒有漏掉重要信息,其他很多支持性的代碼都還沒有包含進去。至少目前有了一個大的輪廓,接下來在深入代碼的時候可以慢慢擴展開。

頂層調(diào)用樹

前面討論了Python源碼的主要目錄結(jié)構(gòu),以及其中主要的源文件。這里我們換一個思路,看看一個Python源文件是如何在Python解釋器里面運行的。調(diào)用Python的可執(zhí)行文件~/.pyenv/versions/3.6.6/bin/python和調(diào)用我們編寫的其他C語言程序在方式上并沒有太大區(qū)別,不同之處在于Python可執(zhí)行文件讀取的Python源文件,并執(zhí)行其中的代碼。Python之于C就如同C之于匯編,只是Python編譯的字節(jié)碼在Python虛擬機上運行,匯編代碼直接在物理機上運行(嚴格來說還需要轉(zhuǎn)換成機器代碼)。

以下面這條Python源文件運行為例來考察Python可執(zhí)行文件的執(zhí)行過程(大家可以玩玩這個生命游戲,運氣好能看到滑翔機)。

$ python ~/.pyenv/sources/3.6.6/Python-3.6.6/Tools/demo/life.py

既然Python的可執(zhí)行文件是C語言編譯成的,那么一定有C語言的入口函數(shù)main,它就位于Python源碼的./Programs/python.c文件中。

int
main(int argc, char **argv)
{
    // ...
    res = Py_Main(argc, argv_copy);
    // ...
}

順藤摸瓜,我們可以梳理出調(diào)用樹的主干部分。下面的樹形結(jié)構(gòu)中,冒號左邊為函數(shù)名,右邊表示函數(shù)定義所在的C源文件,樹形結(jié)構(gòu)表示函數(shù)定義中包含的其他函數(shù)嵌套調(diào)用。

main: Programs/python.c
└─ Py_Main: Modules/main.c
   ├─ Py_Initialize: Python/pylifecycle.c
   │  ├─ PyInterpreterState_New: Python/pystate.c
   │  ├─ PyThreadState_New: Python/pystate.c
   │  ├─ _PyGILState_Init: Python/pystate.c
   │  └─ _Py_ReadyTypes: Objects/object.c
   ├─ run_file: Modules/main.c
   │  └─ PyRun_FileExFlags: Python/pythonrun.c
   │     ├─ PyParser_ASTFromFileObject: Python/pythonrun.c
   │     │  ├─ PyParser_ParseFileObject: Parser/parsetok.c
   │     │  └─ PyAST_FromNodeObject: Python/ast.c
   │     └─ run_mod: Python/pythonrun.c
   │        ├─ PyAST_CompileObject: Python/compile.c
   │        └─ PyEval_EvalCode: Python/ceval.c
   │           ├─ PyFrame_New: Objects/frameobject.c
   │           └─ PyEval_EvalFrameEx: Python/ceval.c
   └─ Py_FinalizeEx: Python/pylifecycle.c

不得不說,Python源碼的可讀性非常好,這些函數(shù)的命名方式都是自解釋的。Python源文件的運行大致分為兩個步驟:

Py_Initialize:初始化過程,主要涉及到解釋器狀態(tài)、線程狀態(tài)、全局解釋器鎖以及內(nèi)置類型的初始化。

run_file:運行源文件,可以分為三個小步驟

PyParser_ASTFromFileObject:對源文件的文本進行語法分析,得到抽象語法樹。

PyAST_CompileObject:將抽象語法樹編譯成PyCodeObject對象。

PyEval_EvalCode:在Python虛擬機中運行PyCodeObject對象。

Py_FinalizeEx:源文件執(zhí)行結(jié)束后的清理工作。

用流程圖的形式表示上述調(diào)用樹的主干部分應(yīng)該更加清晰明了。

需要指出的是,解釋器循環(huán)真正執(zhí)行的是PyEval_EvalFrameEx函數(shù),它的參數(shù)是PyFrameObject對象,該對象為PyCodeObject對象提供了執(zhí)行的上下文環(huán)境,所以PyFrameObjectPyCodeObject都是非常核心的對象。Python提供了一些工具讓我們可以查看編譯后的代碼對象,即對編譯好的函數(shù)進行反匯編。下面的例子雖然簡單,但已經(jīng)能給人清晰的直觀認識

>>> from dis import dis
>>> class C(object):
...     def __init__(self, x):
...         self.x = x
...     def add(self, y):
...         return self.x + y
...
>>> dis(C)
Disassembly of __init__:
  3           0 LOAD_FAST                1 (x)
              2 LOAD_FAST                0 (self)
              4 STORE_ATTR               0 (x)
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

Disassembly of add:
  5           0 LOAD_FAST                0 (self)
              2 LOAD_ATTR                0 (x)
              4 LOAD_FAST                1 (y)
              6 BINARY_ADD
              8 RETURN_VALUE

反編譯的結(jié)果是一系列的操作碼。頭文件Include/opcode.h包含了Python虛擬機的所有操作碼。能看出上面simple_tuplesimple_list這兩個函數(shù)反編譯后的最大區(qū)別么?tuple是作為常量被加載進來的,而list的生成還需要調(diào)用BUILD_LIST。原因在于tuple在Python的運行時會進行緩存,也就是每次使用無需請求操作系統(tǒng)內(nèi)核以獲得內(nèi)存空間。對比一下使用tuplelist的耗時情況

>>> %timeit x = (1, 2, 3)
10.9 ns ± 0.0617 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
>>> %timeit x = [1, 2, 3]
46.5 ns ± 0.186 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

從統(tǒng)計結(jié)果能看出,tuple的在效率上的優(yōu)勢非常明顯。如果某一段調(diào)用特別頻繁的代碼中有些list可以替換成tuple,千萬不要猶豫。

總結(jié)

我們可以試著為文章開頭第一個問題提供一些思路。我們知道,對計算機做任何形式上的抽象都有可能傷害到計算的效率,對于Python來說有以下幾點

Python對象的內(nèi)存部署方式是以在滿足一定效率的前提下足夠通用為目標的,因此在面臨特定問題時它不一定是最優(yōu)的。

Python是動態(tài)類型語言,并不是編譯型語言,導(dǎo)致代碼在運行時是可變的,從Python將抽象語法樹PyCodeObject對象暴露出來這一點就能看出。

全局解釋器鎖也會妨礙使用多進程來實現(xiàn)性能的提升。

Python虛擬機作為對CPU硬件的抽象也是沒法甩鍋的。

所以為了提高Python程序的效率,我們需要深入了解Python對象的實現(xiàn)原理、PyCodeObject的特性以及全局解釋器和Python虛擬機的限制。之于文章開頭的其他問題,我們將隨著Python源碼的深入研究慢慢展開。

現(xiàn)在我們對Python代碼的運行有了一個宏觀的理解,而且大量的細節(jié)都有待深入研究。通過對調(diào)用樹主干部分的梳理,能看出其他比較重要的支持性模塊還包括Python抽象對象PyObject,抽象語法樹及其編譯,PyCodeObject對象,PyFrameObject對象,解釋器狀態(tài),線程狀態(tài),全局解釋器鎖。在以后的文章中,我們會分別對這些模塊進行探討。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/42183.html

相關(guān)文章

  • 2017年2月份前端資源分享

    平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個網(wǎng)址:http://www.kancloud.cn/jsfron... 1. Javascript 前端生成好看的二維碼 十大經(jīng)典排序算法(帶動圖演示) 為什么知乎前端圈普遍認為H5游戲和H5展示的JSer 個人整理和封裝的YU.js庫|中文詳細注釋|供新手學(xué)習(xí)使用 擴展JavaScript語法記錄 - 掉坑初期工具 漢字拼音轉(zhuǎn)換...

    lily_wang 評論0 收藏0
  • 2017年2月份前端資源分享

    平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個網(wǎng)址:http://www.kancloud.cn/jsfron... 1. Javascript 前端生成好看的二維碼 十大經(jīng)典排序算法(帶動圖演示) 為什么知乎前端圈普遍認為H5游戲和H5展示的JSer 個人整理和封裝的YU.js庫|中文詳細注釋|供新手學(xué)習(xí)使用 擴展JavaScript語法記錄 - 掉坑初期工具 漢字拼音轉(zhuǎn)換...

    chengjianhua 評論0 收藏0
  • 2017年2月份前端資源分享

    平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個網(wǎng)址:http://www.kancloud.cn/jsfron... 1. Javascript 前端生成好看的二維碼 十大經(jīng)典排序算法(帶動圖演示) 為什么知乎前端圈普遍認為H5游戲和H5展示的JSer 個人整理和封裝的YU.js庫|中文詳細注釋|供新手學(xué)習(xí)使用 擴展JavaScript語法記錄 - 掉坑初期工具 漢字拼音轉(zhuǎn)換...

    Anonymous1 評論0 收藏0
  • 2017年2月份前端資源分享

    平日學(xué)習(xí)接觸過的網(wǎng)站積累,以每月的形式發(fā)布。2017年以前看這個網(wǎng)址:http://www.kancloud.cn/jsfron... 1. Javascript 前端生成好看的二維碼 十大經(jīng)典排序算法(帶動圖演示) 為什么知乎前端圈普遍認為H5游戲和H5展示的JSer 個人整理和封裝的YU.js庫|中文詳細注釋|供新手學(xué)習(xí)使用 擴展JavaScript語法記錄 - 掉坑初期工具 漢字拼音轉(zhuǎn)換...

    dreamtecher 評論0 收藏0
  • AI開發(fā)書籍分享

    摘要:編程書籍的整理和收集最近一直在學(xué)習(xí)深度學(xué)習(xí)和機器學(xué)習(xí)的東西,發(fā)現(xiàn)深入地去學(xué)習(xí)就需要不斷的去提高自己算法和高數(shù)的能力然后也找了很多的書和文章,隨著不斷的學(xué)習(xí),也整理了下自己的學(xué)習(xí)筆記準備分享出來給大家后續(xù)的文章和總結(jié)會繼續(xù)分享,先分享一部分的 編程書籍的整理和收集 最近一直在學(xué)習(xí)deep learning深度學(xué)習(xí)和機器學(xué)習(xí)的東西,發(fā)現(xiàn)深入地去學(xué)習(xí)就需要不斷的去提高自己算法和高數(shù)的能力然后...

    huayeluoliuhen 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<