五盐数、PyCodeObject與Python程序執(zhí)行

一棒拂、Python程序執(zhí)行原理

1.一個小程序

# [demo.py]
class A(object):
  pass

def func():
  a = 5
  b = 2
  print 'hello coco!'

a = A()
func()

對于如上一個簡單程序,稍有python編程經(jīng)驗都能理輕松理解玫氢。執(zhí)行指令:

python demo.py

如我們預(yù)期帚屉,程序會產(chǎn)生執(zhí)行結(jié)果:

hello coco!

2.執(zhí)行流程

如上所示,一個文本文件demo.py漾峡,經(jīng)過python施加魔法后變成機器指令并執(zhí)行起來攻旦。那么python內(nèi)部究竟是如何運作呢?如我們所知生逸,python是一門解釋性語言牢屋,它的執(zhí)行流程與Java且预、C#這些解釋型語言一樣可以用兩個詞概括編譯、解析烙无。

1)編譯

對于上述的python程序锋谐,執(zhí)行后細(xì)心的同學(xué)會發(fā)現(xiàn)程序文件夾多了一個demo.pyc文件。事實上皱炉,這個pyc文件就是對demo.py文件的編譯結(jié)果。編譯結(jié)果是一個稱之為python字節(jié)碼序列(為運行時Python虛擬機所執(zhí)行的指令)狮鸭。python字節(jié)碼與機器指令碼很相似:

12 MAKE_FUNCTION            0
15 CALL_FUNCTION            0
18 BUILD_CLASS         
19 STORE_NAME   

先把python文件編譯成字節(jié)碼主要有兩個好處合搅,第一方面也是最重要的:跨平臺是python的一大特性,不同機器的機器指令是不同的歧蕉,將代碼先編譯為python解釋器識別的字節(jié)碼灾部,運行時可以根據(jù)不同機器執(zhí)行相應(yīng)的機器指令;第二方面惯退,將python先編譯成字節(jié)碼可以提升運行性能赌髓,將編譯內(nèi)容事先存儲到pyc文件中(pyc文件中不單存儲了字節(jié)碼信息),如無修改運行時無需再次編譯催跪。

2)解釋

python解釋器首先將python文件編譯為字節(jié)碼锁蠕,解釋為字節(jié)碼后python的解析器-python虛擬機就開始接手所有的工作:依次讀入每條字節(jié)碼指令并逐條執(zhí)行。

二懊蒸、PyCodeObject

如上我們大概了解了python的執(zhí)行流程以及python字節(jié)碼的概念荣倾,接下來將深入源碼探索python實現(xiàn)這些機制的內(nèi)部細(xì)節(jié)。首先看python的編譯過程骑丸,如前說到python將編譯結(jié)果字節(jié)碼存儲到pyc文件中舌仍,事實上這個結(jié)論還不夠全面。pyc中除了字節(jié)碼還存儲了很多python程序運行時信息包括定義的字符串通危、常量等铸豁。python的編譯結(jié)果的的奧秘全部藏在PyCodeObject中,PyCodeObject是python中的一個命名空間(命名空間指的是有獨立變量定義的Code block如函數(shù)菊碟、類节芥、模塊等)的編譯結(jié)果在內(nèi)存中的表示。

/* code.h */
/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;        /* #arguments, except *args */
    int co_nlocals;     /* #local variables */
    int co_stacksize;       /* #entries needed for evaluation stack */
    int co_flags;       /* CO_..., see below */
    PyObject *co_code;      /* instruction opcodes */
    PyObject *co_consts;    /* list (constants used) */
    PyObject *co_names;     /* list of strings (names used) */
    PyObject *co_varnames;  /* tuple of strings (local variable names) */
    PyObject *co_freevars;  /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest doesn't count for hash/cmp */
    PyObject *co_filename;  /* string (where it was loaded from) */
    PyObject *co_name;      /* string (name, for reference) */
    int co_firstlineno;     /* first source line number */
    PyObject *co_lnotab;    /* string (encoding addr<->lineno mapping) See Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;     /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
} PyCodeObject;

從源碼中可以看到PyCodeObject中包含了co_argcount和co_nlocals等字段逆害,這些字段的內(nèi)容如下表:

  • co_nlocals : Code Block中局部變量個數(shù)藏古,包括其位置參數(shù)個數(shù)
  • co_stacksize : 執(zhí)行該段Code Block需要的棧空間
  • co_code : Code Block編譯得到的字節(jié)碼指令序列忍燥,以PyStringObject的形式存在
  • co_consts: PyTupleObject拧晕,保存Code Block中的所有常量
  • co_names: PyTupleObject, 保存Code Block中的所有符號
  • co_varnames: Code Block中的局部變量名集合
  • co_freevars : Python實現(xiàn)閉包存儲內(nèi)容
  • co_cellvars : Code Block中內(nèi)部嵌套函數(shù)所引用的局部變量名集合
  • co_filename : Code Block對應(yīng)的.py文件的完整路徑
  • co_name : Code Block的名字,通常是函數(shù)名或類名

python層的code對象與PyCodeObject對應(yīng)梅垄,通過python的compile接口可以查看code對象(即PyCodeObject對象)厂捞。

>>> source = open('demo.py').read()
>>> co = compile(source,'demo.py', 'exec')
>>> type(co)
<type 'code'>
>>> dir(co)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', 
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', 
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
'__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 
'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 
'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> co.co_names
('object', 'A', 'func', 'a')
>>> co.co_filename
'demo.py'

三输玷、持久化PyCodeObject

PyCodeObject中不僅存儲了代碼對應(yīng)的字節(jié)碼指令序列,還保存了代碼的運行時信息靡馁。PyCodeObject是這些信息在內(nèi)存中的表示秧饮,為以后執(zhí)行能重復(fù)利用這些編譯信息,減少編譯時間(在代碼未改變的情況下)浅缸,python解釋器會把這些信息序列化到pyc文件中润讥。

/*import.c*/
static void write_compiled_module(PyCodeObject *co, char *cpathname, struct stat *srcstat, time_t mtime)
{
    PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);
    /* First write a 0 for mtime */
    PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION);
    PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);
}

pyc文件中會記錄把編譯的版本號,編譯時間胧弛、PyCodeObject等信息序列化到pyc文件中尤误,實際的序列化過程在marshal.c中實現(xiàn):

/*marshal.c*/
void PyMarshal_WriteObjectToFile(PyObject *x, FILE *fp, int version)
{
    WFILE wf;
    wf.fp = fp;
    wf.error = WFERR_OK;
    wf.depth = 0;
    wf.strings = (version > 0) ? PyDict_New() : NULL;
    wf.version = version;
    w_object(x, &wf);
    Py_XDECREF(wf.strings);
}

static void w_object(PyObject *v, WFILE *p)
{
    Py_ssize_t i, n;

    p->depth++;

    if (p->depth > MAX_MARSHAL_STACK_DEPTH) {
        p->error = WFERR_NESTEDTOODEEP;
    }
    else if (v == NULL) {
        w_byte(TYPE_NULL, p);
    }
    else if ...

四、Python字節(jié)碼

在opcode.h中定義了python的字節(jié)碼指令定義结缚。

/* opcode.h*/
/* Instruction opcodes for compiled code */
#define LOAD_CONST  100 /* Index in const list */
#define LOAD_NAME   101 /* Index in name list */
#define BUILD_TUPLE 102 /* Number of tuple items */
#define BUILD_LIST  103 /* Number of list items */
#define BUILD_SET   104     /* Number of set items */
#define BUILD_MAP   105 /* Always zero for now */
#define LOAD_ATTR   106 /* Index in name list */

python中也提供了dis工具可以查看PyCodeObject對應(yīng)的字節(jié)碼指令:

>>> source = open('demo.py').read()
>>> co = compile(source, 'demo.py', 'exec')
>>> import dis
>>> dis.dis(co)
  1           0 LOAD_CONST               0 ('A')
              3 LOAD_NAME                0 (object)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               1 (<code object A at 0x7f993a73adb0, file "demo.py", line 1>)
             12 MAKE_FUNCTION            0
             15 CALL_FUNCTION            0
             18 BUILD_CLASS         
             19 STORE_NAME               1 (A)

  4          22 LOAD_CONST               2 (<code object func at 0x7f993a662a30, file "demo.py", line 4>)
             25 MAKE_FUNCTION            0
             28 STORE_NAME               2 (func)

  9          31 LOAD_NAME                1 (A)
             34 CALL_FUNCTION            0
             37 STORE_NAME               3 (a)

 10          40 LOAD_NAME                2 (func)
             43 CALL_FUNCTION            0
             46 POP_TOP             
             47 LOAD_CONST               3 (None)
             50 RETURN_VALUE  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末损晤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子红竭,更是在濱河造成了極大的恐慌尤勋,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茵宪,死亡現(xiàn)場離奇詭異最冰,居然都是意外死亡,警方通過查閱死者的電腦和手機稀火,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門锌奴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人憾股,你說我怎么就攤上這事鹿蜀。” “怎么了服球?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵茴恰,是天一觀的道長。 經(jīng)常有香客問我斩熊,道長往枣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任粉渠,我火速辦了婚禮分冈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘霸株。我一直安慰自己雕沉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布去件。 她就那樣靜靜地躺著坡椒,像睡著了一般扰路。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倔叼,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天汗唱,我揣著相機與錄音,去河邊找鬼丈攒。 笑死哩罪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巡验。 我是一名探鬼主播际插,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼深碱!你這毒婦竟也來了腹鹉?” 一聲冷哼從身側(cè)響起藏畅,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤敷硅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后愉阎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绞蹦,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年榜旦,在試婚紗的時候發(fā)現(xiàn)自己被綠了幽七。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡溅呢,死狀恐怖澡屡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咐旧,我是刑警寧澤驶鹉,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站铣墨,受9級特大地震影響室埋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伊约,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一姚淆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屡律,春花似錦腌逢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骤肛。三九已至,卻和暖如春窍蓝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吓笙。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留土涝,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像溯祸,于是被迫代替她去往敵國和親焦辅。 傳聞我的和親對象是個殘疾皇子筷登,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容