一、Python程序执行原理

1.一个小程序

# [demo.py]
class A(object):
pass
def func():
a = 5
b = 2
print 'hello coco!'
a = A()
func()

对于如上一个简单程序,稍有python编程经验都能理轻松理解。执行指令:

python demo.py

如我们预期,程序会产生执行结果:

hello coco!

2.执行流程

如上所示,一个文本文件demo.py,经过python施加魔法后变成机器指令并执行起来。那么python内部究竟是如何运作呢?如我们所知,python是一门解释性语言,它的执行流程与Java、C#这些解释型语言一样可以用两个词概括编译、解析。

1)编译

对于上述的python程序,执行后细心的同学会发现程序文件夹多了一个demo.pyc文件。事实上,这个pyc文件就是对demo.py文件的编译结果。编译结果是一个称之为python字节码序列(为运行时Python虚拟机所执行的指令)。python字节码与机器指令码很相似:

12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_NAME

先把python文件编译成字节码主要有两个好处,第一方面也是最重要的:跨平台是python的一大特性,不同机器的机器指令是不同的,将代码先编译为python解释器识别的字节码,运行时可以根据不同机器执行相应的机器指令;第二方面,将python先编译成字节码可以提升运行性能,将编译内容事先存储到pyc文件中(pyc文件中不单存储了字节码信息),如无修改运行时无需再次编译。

2)解释

python解释器首先将python文件编译为字节码,解释为字节码后python的解析器-python虚拟机就开始接手所有的工作:依次读入每条字节码指令并逐条执行。

二、PyCodeObject

如上我们大概了解了python的执行流程以及python字节码的概念,接下来将深入源码探索python实现这些机制的内部细节。首先看python的编译过程,如前说到python将编译结果字节码存储到pyc文件中,事实上这个结论还不够全面。pyc中除了字节码还存储了很多python程序运行时信息包括定义的字符串、常量等。python的编译结果的的奥秘全部藏在PyCodeObject中,PyCodeObject是python中的一个命名空间(命名空间指的是有独立变量定义的Code block如函数、类、模块等)的编译结果在内存中的表示。

/* 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 addrlineno 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等字段,这些字段的内容如下表:

co_nlocals : Code Block中局部变量个数,包括其位置参数个数

co_stacksize : 执行该段Code Block需要的栈空间

co_code : Code Block编译得到的字节码指令序列,以PyStringObject的形式存在

co_consts: PyTupleObject,保存Code Block中的所有常量

co_names: PyTupleObject, 保存Code Block中的所有符号

co_varnames: Code Block中的局部变量名集合

co_freevars : Python实现闭包存储内容

co_cellvars : Code Block中内部嵌套函数所引用的局部变量名集合

co_filename : Code Block对应的.py文件的完整路径

co_name : Code Block的名字,通常是函数名或类名

python层的code对象与PyCodeObject对应,通过python的compile接口可以查看code对象(即PyCodeObject对象)。

>>> source = open('demo.py').read()
>>> co = compile(source,'demo.py', 'exec')
>>> type(co)
>>> 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中不仅存储了代码对应的字节码指令序列,还保存了代码的运行时信息。PyCodeObject是这些信息在内存中的表示,为以后执行能重复利用这些编译信息,减少编译时间(在代码未改变的情况下),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中实现:

/*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字节码

在opcode.h中定义了python的字节码指令定义。

/* 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对应的字节码指令:

>>> 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 ()
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_NAME 1 (A)
4 22 LOAD_CONST 2 ()
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