python源碼分析筆記(8)

1.Python程序的執行過程

實際上Python和java,C#執行原理都可以用兩個詞概括,------虛擬機,字節碼

Python有一個非常核心的東西,這個東西被稱爲解釋器。當我運行一個程序時,例如 python my-program.py ,Python解釋器立即被激活,然後開始執行,在運行之前,還要完成一個複製的工作,編譯py.文件,結果主要產生一組Python的byte-code(字節碼),然後將編譯結果交給Python的虛擬機。

 

[demo.py]
class A:
    pass


def fun():
    pass

a = A()
fun()

當我們在執行demo.py時,編譯結果會產生PyCodeObject對象和pyc文件。在程序運行期間,編譯結果存在於內存的PyCodeObject對象中,而Python結束運行後,編譯結果又被保存到了pyc文件,當下一次運行相同的程序時,Python會根據pyc文件中記錄的編譯結果直接建立內存中的PyCodeObject對象,而不用再次編譯。

python中PyCodeObject的聲明:

對於代碼中的一個Code Block,會創建一個PyCodeObject對象與這段代碼對應,對應上文的demo.py,編譯完成後總共會創建三個PyCodeObject對象,一個對應demo.py整個文件的,一個是對應class A所代表的Code Block,而最後一個對應def Fun所代表的Code Block.

2.pyc文件

每一個PyCodeObject對象中包含了每一個Code Block中Python源代碼經過編譯後得到的byte code序列。而前面提到,Python會將這些字節碼序列和PyCodeObject對象一起存儲在pyc文件。但是我們在命令行敲下python demo.py時並沒有生成pyc文件,原因是有寫程序可能執行一次就再也不執行了,沒必要生成其對應的pyc文件。但是假如碰到import demo的動態加載動作之後,python就會產生pyc文件了。意味着如果Python碰到import demo語句,會首先到設定好的PATH中尋找abc.pyc。如果沒有,只發現了demo.py,那麼就會編譯成PyCodeObject對應的中間結果,然後創建pyc文件,接下來Python纔會對abc.pyc進行import動作。

其實pyc是一個二進制文件,那麼python如何解釋這一堆看上去毫無意義的字節流就至關重要了。要了解pyc文件的格式,首先必須要清楚PyCodeObject中每一個域都表示什麼含義。

3.Python的字節碼

關於Python的編譯結果,我們還剩下最後一個話題了,那就是Python的字節碼,這裏只會做一個簡單的介紹,我們知道Python源代碼在執行前會被編譯成Python的字節碼指令序列,Python虛擬機就是根據這些字節碼進行一系列的操作。在Python安裝目錄的Include文件夾中,opcode.h保存了這些字節碼序列

#define STOP_CODE   0
#define POP_TOP     1
#define ROT_TWO     2
#define ROT_THREE   3
#define DUP_TOP     4
#define ROT_FOUR    5
#define NOP     9

#define UNARY_POSITIVE  10
#define UNARY_NEGATIVE  11
#define UNARY_NOT   12
#define UNARY_CONVERT   13

#define UNARY_INVERT    15

#define LIST_APPEND 18
#define BINARY_POWER    19

#define BINARY_MULTIPLY 20
#define BINARY_DIVIDE   21
#define BINARY_MODULO   22
#define BINARY_ADD  23
#define BINARY_SUBTRACT 24
#define BINARY_SUBSCR   25
#define BINARY_FLOOR_DIVIDE 26
#define BINARY_TRUE_DIVIDE 27
#define INPLACE_FLOOR_DIVIDE 28
#define INPLACE_TRUE_DIVIDE 29

#define SLICE       30
/* Also uses 31-33 */

#define STORE_SLICE 40
/* Also uses 41-43 */

#define DELETE_SLICE    50
/* Also uses 51-53 */

#define STORE_MAP   54
#define INPLACE_ADD 55
#define INPLACE_SUBTRACT    56
#define INPLACE_MULTIPLY    57
#define INPLACE_DIVIDE  58
#define INPLACE_MODULO  59
#define STORE_SUBSCR    60
#define DELETE_SUBSCR   61

#define BINARY_LSHIFT   62
#define BINARY_RSHIFT   63
#define BINARY_AND  64
#define BINARY_XOR  65
#define BINARY_OR   66
#define INPLACE_POWER   67
#define GET_ITER    68

#define PRINT_EXPR  70
#define PRINT_ITEM  71
#define PRINT_NEWLINE   72
#define PRINT_ITEM_TO   73
#define PRINT_NEWLINE_TO 74
#define INPLACE_LSHIFT  75
#define INPLACE_RSHIFT  76
#define INPLACE_AND 77
#define INPLACE_XOR 78
#define INPLACE_OR  79
#define BREAK_LOOP  80
#define WITH_CLEANUP    81
#define LOAD_LOCALS 82
#define RETURN_VALUE    83
#define IMPORT_STAR 84
#define EXEC_STMT   85
#define YIELD_VALUE 86
#define POP_BLOCK   87
#define END_FINALLY 88
#define BUILD_CLASS 89

#define HAVE_ARGUMENT   90  /* Opcodes from here have an argument: */

#define STORE_NAME  90  /* Index in name list */
#define DELETE_NAME 91  /* "" */
#define UNPACK_SEQUENCE 92  /* Number of sequence items */
#define FOR_ITER    93

#define STORE_ATTR  95  /* Index in name list */
#define DELETE_ATTR 96  /* "" */
#define STORE_GLOBAL    97  /* "" */
#define DELETE_GLOBAL   98  /* "" */
#define DUP_TOPX    99  /* number of items to duplicate */
#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_MAP   104 /* Always zero for now */
#define LOAD_ATTR   105 /* Index in name list */
#define COMPARE_OP  106 /* Comparison operator */
#define IMPORT_NAME 107 /* Index in name list */
#define IMPORT_FROM 108 /* Index in name list */

#define JUMP_FORWARD    110 /* Number of bytes to skip */
#define JUMP_IF_FALSE   111 /* "" */
#define JUMP_IF_TRUE    112 /* "" */
#define JUMP_ABSOLUTE   113 /* Target byte offset from beginning of code */

#define LOAD_GLOBAL 116 /* Index in name list */

#define CONTINUE_LOOP   119 /* Start of loop (absolute) */
#define SETUP_LOOP  120 /* Target address (relative) */
#define SETUP_EXCEPT    121 /* "" */
#define SETUP_FINALLY   122 /* "" */

#define LOAD_FAST   124 /* Local variable number */
#define STORE_FAST  125 /* Local variable number */
#define DELETE_FAST 126 /* Local variable number */

#define RAISE_VARARGS   130 /* Number of raise arguments (1, 2 or 3) */
/* CALL_FUNCTION_XXX opcodes defined below depend on this definition */
#define CALL_FUNCTION   131 /* #args + (#kwargs<<8) */
#define MAKE_FUNCTION   132 /* #defaults */
#define BUILD_SLICE     133 /* Number of items */

#define MAKE_CLOSURE    134     /* #free vars */
#define LOAD_CLOSURE    135     /* Load free variable from closure */
#define LOAD_DEREF      136     /* Load and dereference from closure cell */ 
#define STORE_DEREF     137     /* Store into cell */ 

/* The next 3 opcodes must be contiguous and satisfy
   (CALL_FUNCTION_VAR - CALL_FUNCTION) & 3 == 1  */
#define CALL_FUNCTION_VAR          140  /* #args + (#kwargs<<8) */
#define CALL_FUNCTION_KW           141  /* #args + (#kwargs<<8) */
#define CALL_FUNCTION_VAR_KW       142  /* #args + (#kwargs<<8) */

/* Support for opargs more than 16 bits long */
#define EXTENDED_ARG  143

4.解析pyc文件

好了到現在,關於PyCodeObject的pyc文件已經有了初步瞭解,我們可以用PycParser的工程對pyc進行解析,轉化爲可視的XML,在co_consts中,包含了另外的PyCodeObject,同時還包含了別的對象,實際上在co_consts中包含了Python源文件的中所定義的所有常量對象,即除了PyStringObject對象之外的所有對象。PyStringObject通常保存在co_names和co_varnames。

我們知道在生成pyc時,會將PyCodeObject對象中的字節碼序列一起寫入pyc文件中,而且這個pyc文件還記錄了每一條字節碼指令與Python源代碼行號的對應關係,保存在co_Inotab中。

而 Python 庫中 dis 的 dis 方法可以對 code對象 進行解析。接收 code對象,輸出 字節碼指令信息。

dis.dis 的輸出:

  • 第一列,是 字節碼指令 對應的 源代碼 在 Python 程序中的行數
  • 第二列,是當前 字節碼指令 在 co_code 中的偏移位置
  • 第三列,當前的字節碼指令
  • 第四列,當前字節碼指令的參數

test.py

import sys

a = 1

def b():
    print a
    a = 2
    print a
>>> source = open('/Users/chao/Desktop/test.py').read()
>>> co = compile(source, 'test.py', 'exec')
>>> import dis
>>> dis.dis(co)
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (sys)
              9 STORE_NAME               0 (sys)

  3          12 LOAD_CONST               2 (1)
             15 STORE_NAME               1 (a)

  5          18 LOAD_CONST               3 (<code object b at 0x1005dc930, file "test.py", line 5>)
             21 MAKE_FUNCTION            0
             24 STORE_NAME               2 (b)
             27 LOAD_CONST               1 (None)
             30 RETURN_VALUE
>>> 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']
>>> print co.co_names
('sys', 'a', 'b')
>>> print co.co_name
<module>
>>> print co.co_filename
test.py

 

 

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