阅读条件:
C语言基础, Python爱好者
前言
前面我们已经谈到过python面向对象的实现.今天我们来看看Python虚拟机的基础.
主要内容分为两个部分:
- Python源码编译后的结果: .pyc文件和PyCodeObject对象.
- Python的模拟函数栈帧: PyFrameObject对象.
编译Python源码
大多时候,我们运行python代码都不会关注源码的编译结果.因为python虚拟机是将编译和运行两部分合二为一了.
而另一种常见的解释型语言java,则将两部分分开,分别是: javac
和java
.我们通过javac
命令来生成java源码对应的class文件,再通过java命令来运行.
事实上,python也可以达到同样的结果.我们通过以下的命令可以生成.pyc
文件.
1
|
python -m py_compile <source_code.py>
|
事实上,.pyc
和class文件应该基本类似的.
pyc文件的内容
pyc文件主要由三部分内容组成:
- magic number
主要标识了Python的版本,不同版本的python所支持的指令是不同的.所以Python2.7编译而成的pyc文件是不能由Python2.5去运行的.
- 编译时间
这个主要用于决定是否重新编译pyc文件.如果源码的更新时间晚于pyc的生成时间,那么肯定是要重新编译的.
- 编译结果
这个是真正的戏肉.是源码对应的指令和一系列信息的集合.是PyCodeObject对象序列化的结果.
PyCodeObject对象
Python是一门解释型语言,它对源码进行了编译.本质上,就是将源码中的信息提取出来,按照它的规则来重新整理.
那么它生成的结果在内存中的体现就是PyCodeObject
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
[Include/code.h]
/* Bytecode object */
typedef struct {
PyObject_HEAD
int co_argcount; /* 一般参数的个数,不包括扩展参数 */
int co_nlocals; /* 局部变量的个数 */
int co_stacksize; /* 运行所需要的栈大小 */
int co_flags; /* 一些标志位: 比如是否是生成器,是否有扩展参数之类的 */
PyObject *co_code; /* Python指令 */
PyObject *co_consts; /* 常量对象 */
PyObject *co_names; /* 名字字符串 */
PyObject *co_varnames; /* 参数变量名元组 */
PyObject *co_freevars; /* 闭包相关: 引用外部变量名 */
PyObject *co_cellvars; /* 闭包相关: 被内部嵌套函数引用的变量名 */
/* The rest doesn't count for hash/cmp */
PyObject *co_filename; /* 文件名 */
PyObject *co_name; /* string (name, for reference) */
int co_firstlineno; /* 指令首行位置 */
PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
} PyCodeObject;
|
其中需要重点关注的是co_consts
和co_names
.
python编译而成的那些对象保存在co_consts
中,尤其其中还可以嵌套其他的PyCodeObject.
局部变量名保存在co_names
中,事实上在执行基本指令时,这两个域使用的相当频繁.
而pyc文件就是将PyCodeObject保存在文件中的结果.执行的时候,还是会将pyc文件恢复成PyCodeObject.
模拟函数运行栈帧
有了PyCodeObject
,我们就有了需要运行的对象.但是我们还需要模拟函数运行时的栈帧.这个工作由PyFrameObject
来承担.
也可以说对于一段代码而言,PyCodeObject
是其静态部分,而PyFrameObject
则是其动态部分.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
[Include/frameobject.h]
typedef struct _frame {
PyObject_VAR_HEAD
struct _frame *f_back; /* previous frame, or NULL */
PyCodeObject *f_code; /* code segment */
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */
PyObject *f_globals; /* global symbol table (PyDictObject) */
PyObject *f_locals; /* local symbol table (any mapping) */
PyObject **f_valuestack; /* points after the last local */
/* Next free slot in f_valuestack. Frame creation sets to f_valuestack.
Frame evaluation usually NULLs it, but a frame that yields sets it
to the current stack top. */
PyObject **f_stacktop;
PyObject *f_trace; /* Trace function */
/* If an exception is raised in this frame, the next three are used to
* record the exception info (if any) originally in the thread state. See
* comments before set_exc_info() -- it's not obvious.
* Invariant: if _type is NULL, then so are _value and _traceback.
* Desired invariant: all three are NULL, or all three are non-NULL. That
* one isn't currently true, but "should be".
*/
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
PyThreadState *f_tstate;
int f_lasti; /* Last instruction if called */
/* As of 2.3 f_lineno is only valid when tracing is active (i.e. when
f_trace is set) -- at other times use PyCode_Addr2Line instead. */
int f_lineno; /* Current line number */
int f_iblock; /* index in f_blockstack */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
} PyFrameObject;
|
其中有些很有趣的域.
- f_back: 表示上一帧.也将各个栈帧串联了起来.
- f_code: 关联PyCodeObject
- f_builtins, f_globals, f_locals: 则表示不同的namespace.还记得LEGB规则么?这就是他们在代码中的实现.
- f_valuestack, f_stacktop: 表示栈顶和栈底.
- f_tstate: 表示线程状态
- f_blockstack: 实现了循环,异常等机制.
- f_localplus: 参数保存的域: 包括函数调用时传递的各种参数.局部参数.闭包相关的参数.
一言难尽,还是自己去读一读<Python源码剖析>.还是挺精彩的.
执行入口
我们有了要执行的代码PyCodeObject
,我们有了函数栈帧PyFrameObject
.那么谁来执行呢?
答案是:Python/ceval.c PyEval_EvalFrameEx
.
是它从PyCodeObject取出一条条指令,在PyFrameObject中执行.
总结
Python运行是在PyFrameObject中.主要是PyCodeObject中的co_consts
和co_names
域和PyFrameObject中的临时栈和f_locals
进行互动.
我知道这部分写的太过简略,这只是我个人的总结.更多的还是要去看书.