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