運行程序
當在shell
中敲入python xx.py
運行 Python 程序時悲雳,就是激活了 Python 解釋器置侍。
Python 解釋器并不會立即運行程序映之,而是會對 Python 程序的源代碼進行編譯拦焚,產(chǎn)生字節(jié)碼,然后將字節(jié)碼交給虛擬機一條條順序執(zhí)行杠输。
源文件中的內(nèi)容可以分為:字符串
赎败、常量
、操作
蠢甲。
操作
會被編譯為字節(jié)碼指令序列
僵刮,字符串
和常量
在編譯的過程中會被收集起來。這些編譯后的信息在程序運行時鹦牛,會作為 運行時對象 PyCodeObject 存儲于內(nèi)存中搞糕。運行結(jié)束后,PyCodeObject 被放入xx.pyc
文件曼追,保存在硬盤中窍仰。這樣,在下次運行時礼殊,可以直接根據(jù).pyc
文件的內(nèi)容驹吮,在內(nèi)存中建立 PyCodeObject ,不需要再進行編譯晶伦。
PyCodeObject
在編譯器對源碼進行編譯時碟狞,會為每一個 Code Block 創(chuàng)建一個對應(yīng)的 PyCodeObject。那么婚陪,什么是 Code Block 呢族沃?規(guī)則是:當進入一個新的名字空間,或者新的作用域泌参,就是進入了一個新 Code Block脆淹。名字空間是符號的上下文環(huán)境,決定了符號的含義沽一。也就是說未辆,決定了變量名對應(yīng)的變量值是什么。
名字空間是可以嵌套的锯玛,能夠形成一個名字空間鏈咐柜,虛擬機在執(zhí)行字節(jié)碼時,一個重要的任務(wù)就是從鏈中確定一個符號的對象是什么攘残。
在 Python 中拙友,類、函數(shù)歼郭、modules 對應(yīng)獨立的名字空間遗契,所以都有對應(yīng)的 PyCodeObject。
PyCodeObject 中co_code
域保存的就是對操作
編譯生成的字節(jié)碼指令序列
病曾。
產(chǎn)生pyc文件的方法
上面提到牍蜂,Python 程序運行結(jié)束后漾根,會在硬盤中以.pyc
文件的形式存儲 PyCodeObject,但直接運行 Python 程序并不會產(chǎn)生.pyc
文件鲫竞。
這可能是因為直接運行的 Python 程序辐怕,有些只是臨時使用一次,所以沒有通過.pyc
保存編譯結(jié)果的必要从绘。
一種常見的寄疏,產(chǎn)生pyc文件的方法是import機制。當Python 程序運行時僵井,如果遇到 import abc陕截,會到設(shè)定好的path中尋找 abc.pyc 文件,如果沒有批什,只找到abc.py农曲,會先將 abc.py 編譯成 CodeObject,然后創(chuàng)建 pyc 文件驻债,將 CodeObject寫入朋蔫,最后才會對 pyc 進行import操作,將 pyc 中的 CodeObject重新復(fù)制到內(nèi)存却汉,并運行。
另外荷并,Python 標準庫中的py_compile
和compile
可以幫助手動產(chǎn)生 pyc 文件合砂。
pyc 文件內(nèi)容是二進制的,想要了解 pyc 文件的格式源织,就要了解 PyCodeObject 中各個域的作用翩伪。
在 Python 中訪問 PyCodeObject
C語言形式的 PyCodeObject 對應(yīng) Python 中的 Code對象,Code對象 是對 PyCodeObject 的簡單包裝谈息。
因此缘屹,可以通過 Code對象 訪問 PyCodeObject 的各個域。這就需要使用 內(nèi)建函數(shù) compile侠仇。
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')
>>> 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
()
>>> print co.co_name
<module>
>>> print co.co_filename
test.py
創(chuàng)建 pyc 文件
一個 pyc 文件包含三部分獨立的信息:
- magic number
- pyc 文件的創(chuàng)建時間信息
- PyCodeObject
import.c
static void
write_compiled_module(PyCodeObject *co, char *cpathname, struct stat *srcstat)
{
FILE *fp;
time_t mtime = srcstat->st_mtime;
#ifdef MS_WINDOWS /* since Windows uses different permissions */
mode_t mode = srcstat->st_mode & ~S_IEXEC;
#else
mode_t mode = srcstat->st_mode & ~S_IXUSR & ~S_IXGRP & ~S_IXOTH;
#endif
fp = open_exclusive(cpathname, mode);
if (fp == NULL) {
if (Py_VerboseFlag)
PySys_WriteStderr(
"# can't create %s\n", cpathname);
return;
}
PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION); # 寫入`magic number`
/* First write a 0 for mtime */
PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION);
PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION); # 寫入`PyCodeObject`
if (fflush(fp) != 0 || ferror(fp)) {
if (Py_VerboseFlag)
PySys_WriteStderr("# can't write %s\n", cpathname);
/* Don't keep partial file */
fclose(fp);
(void) unlink(cpathname);
return;
}
/* Now write the true mtime */
fseek(fp, 4L, 0);
assert(mtime < LONG_MAX);
PyMarshal_WriteLongToFile((long)mtime, fp, Py_MARSHAL_VERSION); # 寫入 pyc 創(chuàng)建時間
fflush(fp);
fclose(fp);
if (Py_VerboseFlag)
PySys_WriteStderr("# wrote %s\n", cpathname);
}
下面一一進行說明
1轻姿,magic number
是 Python 定義的一個整數(shù)值,不同版本定義不同逻炊,用來確保 Python 的兼容性互亮。Python 在加載 pyc 時首先檢查 magic number ,如果與 Python 自身的 magic number 不同余素,說明創(chuàng)建 pyc 的 Python 版本 與 當前版本不兼容豹休,會拒絕加載。
為什么會不兼容呢桨吊?因為字節(jié)碼指令發(fā)生了變化威根,有刪除或增加凤巨。
/* Magic word to reject .pyc files generated by other Python versions.
It should change for each incompatible change to the bytecode.
The value of CR and LF is incorporated so if you ever read or write
a .pyc file in text mode the magic number will be wrong; also, the
Apple MPW compiler swaps their values, botching string constants.
The magic numbers must be spaced apart atleast 2 values, as the
-U interpeter flag will cause MAGIC+1 being used. They have been
odd numbers for some time now.
There were a variety of old schemes for setting the magic number.
The current working scheme is to increment the previous value by
10.
Known values:
Python 1.5: 20121
Python 1.5.1: 20121
Python 1.5.2: 20121
Python 1.6: 50428
Python 2.0: 50823
Python 2.0.1: 50823
Python 2.1: 60202
Python 2.1.1: 60202
Python 2.1.2: 60202
Python 2.2: 60717
Python 2.3a0: 62011
Python 2.3a0: 62021
Python 2.3a0: 62011 (!)
Python 2.4a0: 62041
Python 2.4a3: 62051
Python 2.4b1: 62061
Python 2.5a0: 62071
Python 2.5a0: 62081 (ast-branch)
Python 2.5a0: 62091 (with)
Python 2.5a0: 62092 (changed WITH_CLEANUP opcode)
Python 2.5b3: 62101 (fix wrong code: for x, in ...)
Python 2.5b3: 62111 (fix wrong code: x += yield)
Python 2.5c1: 62121 (fix wrong lnotab with for loops and
storing constants that should have been removed)
Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp)
Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode)
Python 2.6a1: 62161 (WITH_CLEANUP optimization)
.
*/
#define MAGIC (62161 | ((long)'\r'<<16) | ((long)'\n'<<24))
/* Magic word as global; note that _PyImport_Init() can change the
value of this global to accommodate for alterations of how the
compiler works which are enabled by command line switches. */
static long pyc_magic = MAGIC;
2,pyc 創(chuàng)建時間
使得 Python 自動將 pyc 文件與最新的 Python 文件同步洛搀。當對 Python 程序進行編譯產(chǎn)生 pyc 后敢茁,如果后來進行了修改,此時 Python 在嘗試加載 pyc 時姥卢,會發(fā)現(xiàn) pyc 創(chuàng)建時間早于 Python 程序卷要,于是將重新編譯,生成新的 pyc 文件独榴。
3僧叉,PyCodeObject
編譯器會遍歷 PyCodeObject 中的所有域,并依次寫入 pyc棺榔。對于 PyCodeObject 中的每一個對象瓶堕,同樣會進行遍歷,并寫入類型標志
和數(shù)據(jù)(數(shù)值/字符串)
類型標志的三個作用:表明上一個對象的結(jié)束症歇、新對象的開始郎笆、確定新對象的類型
marshal.h
,類型標志
#define TYPE_NULL '0'
#define TYPE_NONE 'N'
#define TYPE_FALSE 'F'
#define TYPE_TRUE 'T'
#define TYPE_STOPITER 'S'
#define TYPE_ELLIPSIS '.'
#define TYPE_INT 'i'
#define TYPE_INT64 'I'
#define TYPE_FLOAT 'f'
#define TYPE_BINARY_FLOAT 'g'
#define TYPE_COMPLEX 'x'
#define TYPE_BINARY_COMPLEX 'y'
#define TYPE_LONG 'l'
#define TYPE_STRING 's'
#define TYPE_INTERNED 't'
#define TYPE_STRINGREF 'R'
#define TYPE_TUPLE '('
#define TYPE_LIST '['
#define TYPE_DICT '{'
#define TYPE_CODE 'c'
#define TYPE_UNICODE 'u'
#define TYPE_UNKNOWN '?'
#define TYPE_SET '<'
#define TYPE_FROZENSET '>'
向 pyc 寫入字符串
部分略
對于嵌套的名字空間忘晤,產(chǎn)生的 PyCodeObject 也是遞歸嵌套的宛蚓,嵌套的 PyCodeObject 在上層 PyCodeObject 的co_consts
中。
字節(jié)碼
源代碼編譯為 字節(jié)碼指令 序列设塔,虛擬機根據(jù)字節(jié)碼進行操作凄吏,完成程序的執(zhí)行,opcode.h
中定義了當前版本 Python 支持的字節(jié)碼指令闰蛔。
字節(jié)碼指令 的編碼并不是按順序增長的痕钢,中間有跳躍。
Include
目錄下的opcode.h
定義了字節(jié)碼指令
#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
解析 pyc
由于包含嵌套 PyCodeObject序六,pyc 中的二進制數(shù)據(jù)實際上是有結(jié)構(gòu)的任连,可以以 XML格式進行解析,從而可視化例诀。使用 pycparser随抠。
而 Python 庫中 dis 的 dis 方法可以對 code對象 進行解析。接收 code對象繁涂,輸出 字節(jié)碼指令信息暮刃。
dis.dis 的輸出:
- 第一列,是 字節(jié)碼指令 對應(yīng)的 源代碼 在 Python 程序中的行數(shù)
- 第二列爆土,是當前 字節(jié)碼指令 在 co_code 中的偏移位置
- 第三列椭懊,當前的字節(jié)碼指令
- 第四列,當前字節(jié)碼指令的參數(shù)
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
參考資料
《Python 源碼剖析》第七章