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