Python3源码分析
本文环境python3.5.2。
参考书籍<<Python源码剖析>>
python官网
Python3启动流程概述
本文基于python3分析其基本的运行过程。作为一门动态语言,python脚本在运行的过程中,实现了编译文件并执行编译文件的过程,这一过程都是基于c语言实现,首先开始介绍一下python3的基本信息。
Python3源码结构
在官网下载python3.5.2后,根据官网的文档目录介绍,主要目录的信息如下:
Doc目录主要是官方文档的说明。
Include目录主要包括了Python的运行的头文件。
Lib目录主要包括了用Python实现的标准库。
Objects目录包括了内建对象。
Parser目录包括了python编译相关代码,将python文件编译生成语法树等相关工作。
Programs目录主要包括了python的入口函数。
Python目录主要包括了Python动态运行时执行的代码,里面包括编译、字节码解释器等工作。
Python的启动流程
根据官网文档目录介绍,启动的时候是运行Python的脚本,Python启动是由Programs下的python.c文件中的main函数开始执行,根据平台的不同选择执行不同类型的main函数,在此已Linux为例;
int
main(int argc, char **argv)
{
...
res = Py_Main(argc, argv_copy);
...
return res;
}
该函数主要就是执行了Py_Main函数,并将该函数的执行结果返回。
继续查看Py_Main函数的执行,在位于Modules/main.c中找到Py_Main函数,
int
Py_Main(int argc, wchar_t **argv)
{
...
Py_Initialize(); // 初始化
...
{
...
if (sts == -1)
sts = run_file(fp, filename, &cf); // 执行python脚本
}
...
Py_Finalize(); // 释放相关资源
...
return sts; // 返回执行结果
}
由此可看出,在调用初始化函数Py_Initialize之后,就会执行run_file方法,当Python脚本执行完成后,就调用Py_Finalize释放相关资源。分别来查看这些函数的执行。
Py_Initialize初始化函数
该函数位于Python/pylifecycle.c中,
void
Py_InitializeEx(int install_sigs)
{
_Py_InitializeEx_Private(install_sigs, 1);
}
void
Py_Initialize(void)
{
Py_InitializeEx(1);
}
直接调用了_Py_InitializeEx_Private方法,并且传入了两个都为1的参数,
void
_Py_InitializeEx_Private(int install_sigs, int install_importlib)
{
...
_PyRandom_Init(); // random模块初始化
interp = PyInterpreterState_New(); // 初始化解释器
if (interp == NULL)
Py_FatalError("Py_Initialize: can't make first interpreter");
tstate = PyThreadState_New(interp); // 初始化线程
if (tstate == NULL)
Py_FatalError("Py_Initialize: can't make first thread");
(void) PyThreadState_Swap(tstate); // 设置tstate为当前运行线程
#ifdef WITH_THREAD
/* We can't call _PyEval_FiniThreads() in Py_Finalize because
destroying the GIL might fail when it is being referenced from
another running thread (see issue #9901).
Instead we destroy the previously created GIL here, which ensures
that we can call Py_Initialize / Py_Finalize multiple times. */
_PyEval_FiniThreads(); // 如果脚本中是多线程运行则创建gil锁
/* Auto-thread-state API */
_PyGILState_Init(interp, tstate);
#endif /* WITH_THREAD */
_Py_ReadyTypes(); // 初始化所有类型,如long,list等
...
interp->modules = PyDict_New(); // 设置解释器的modules为新建的Dict类型
if (interp->modules == NULL) // 如果字典没有生成成功则报错
Py_FatalError("Py_Initialize: can't make modules dictionary");
/* Init Unicode implementation; relies on the codec registry */
if (_PyUnicode_Init() < 0)
Py_FatalError("Py_Initialize: can't initialize unicode");
if (_PyStructSequence_Init() < 0)
Py_FatalError("Py_Initialize: can't initialize structseq");
bimod = _PyBuiltin_Init(); // 内建对象的初始化,如map,None,True等
if (bimod == NULL)
Py_FatalError("Py_Initialize: can't initialize builtins modules");
_PyImport_FixupBuiltin(bimod, "builtins"); // 设置builtins为初始化后的mod
interp->builtins = PyModule_GetDict(bimod); // 设置解释器的builtins字段为初始化后的内建对象
if (interp->builtins == NULL)
Py_FatalError("Py_Initialize: can't initialize builtins dict");
Py_INCREF(interp->builtins); // 增加内建对象的引用
/* initialize builtin exceptions */
_PyExc_Init(bimod); // 导入内建的错误类型,如BaseException,Exception等
sysmod = _PySys_Init(); // 初始化sys模块
if (sysmod == NULL)
Py_FatalError("Py_Initialize: can't initialize sys");
interp->sysdict = PyModule_GetDict(sysmod); // 设置解释器的sys模块
if (interp->sysdict == NULL)
Py_FatalError("Py_Initialize: can't initialize sys dict");
Py_INCREF(interp->sysdict);
_PyImport_FixupBuiltin(sysmod, "sys"); // 将初始化的sysmod设置成模块sys
PySys_SetPath(Py_GetPath()); // 设置导入路径
PyDict_SetItemString(interp->sysdict, "modules",
interp->modules);
...
initmain(interp); /* Module __main__ */ // 创建主main
...
}
该函数主要进行了相关类型的初始化,并初始化了解释器,初始化了线程对象,并设置了相关的内建mod等初始化工作。当初始化工作完成后,就调用了run_file来解释并执行脚本。
run_file编译并执行
该函数位于main.c文件中,
static int
run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
{
...
run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf); // 解释执行
...
return run != 0;
}
真正调用了位于Python/pythonrun.c中的PyRun_AnyFileExFlags方法来解释执行,
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
{
if (filename == NULL)
filename = "???";
if (Py_FdIsInteractive(fp, filename)) { // 是否是交互模式
int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
if (closeit)
fclose(fp);
return err;
}
else
return PyRun_SimpleFileExFlags(fp, filename, closeit, flags); // 执行脚本
}
此处讨论的是脚本模式,此时就会进入PyRun_SimpleFileExFlags函数执行,
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
{
...
else {
...
v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
closeit, flags); // 在没有pyc的情况下执行
}
...
}
此时就调用了PyRun_FileExFlags函数进行执行,
PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
PyObject *locals, int closeit, PyCompilerFlags *flags)
{
...
mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
flags, NULL, arena); // 解析传入的脚本,解析成AST
...
ret = run_mod(mod, filename, globals, locals, flags, arena); // 将AST编译成字节码然后启动字节码解释器执行编译结果
...
}
查看run_mod函数,
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
PyCompilerFlags *flags, PyArena *arena)
{
...
co = PyAST_CompileObject(mod, filename, flags, -1, arena); // 将AST编译成字节码
...
v = PyEval_EvalCode((PyObject*)co, globals, locals); // 解释执行编译的字节码
...
}
再将AST转换为字节码后,调用了PyEval_EvalCode来执行字节码,查看位于Python/ceval.c中的该函数,
PyObject *
PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
{
return PyEval_EvalCodeEx(co,
globals, locals,
(PyObject **)NULL, 0,
(PyObject **)NULL, 0,
(PyObject **)NULL, 0,
NULL, NULL);
}
...
PyObject *
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure)
{
return _PyEval_EvalCodeWithName(_co, globals, locals,
args, argcount, kws, kwcount,
defs, defcount, kwdefs, closure,
NULL, NULL);
}
直接调用了_PyEval_EvalCodeWithName函数来执行,
static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,
PyObject *name, PyObject *qualname)
{
...
retval = PyEval_EvalFrameEx(f,0);
...
}
最终调用了PyEval_EvalFrameEx来执行,该函数就是字节码解释器,实现方式就是一个for循序依次执行字节码,然后调用相关函数去执行,解释出来的脚本,然后返回执行结果。
至此,run_file的大致执行流程如上。
Python3虚拟机框架
通过分析Python的大致执行流程,有了初步了解Python是经历了多少步骤然后调用了哪些函数来进行执行。现在继续分析一下Python的虚拟机框架是如何来实现Python脚本的执行的。
首先我们编写一个简单的Python脚本;
def show():
print("show")
def foo():
print("foo")
show()
foo()
在终端命令行中,直接输入:
python -m dis test.py
终端上打印出该脚本的字节码,如下:
1 0 LOAD_CONST 0 (<code object show at 0x10f2cc8a0, file "test.py", line 1>)
3 LOAD_CONST 1 ('show')
6 MAKE_FUNCTION 0
9 STORE_NAME 0 (show)
4 12 LOAD_CONST 2 (<code object foo at 0x10f2ccd20, file "test.py", line 4>)
15 LOAD_CONST 3 ('foo')
18 MAKE_FUNCTION 0
21 STORE_NAME 1 (foo)
7 24 LOAD_NAME 0 (show)
27 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
30 POP_TOP
8 31 LOAD_NAME 1 (foo)
34 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
37 POP_TOP
38 LOAD_CONST 4 (None)
41 RETURN_VALUE
从中可以看到两个函数foo和show生成了两个code对象,code对象是什么作用呢?
Python的code对象
在Python的编译过程中,一个代码块就对应一个code对象,那Python如何定义一个代码块呢?当编译过程中遇到一个新的命令空间或者作用域时就生成一个code对象,即类或函数都是一个代码块,一个code的类型结构就是PyCodeObject;
typedef struct {
PyObject_HEAD
int co_argcount; /* #arguments, except *args */ // 位置参数的个数
int co_kwonlyargcount; /* #keyword only arguments */
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 aren't used in either hash or comparisons, except for
co_name (used in both) and co_firstlineno (used only in
comparisons). This is done to preserve the name and line number
for tracebacks and debuggers; otherwise, constant de-duplication
would collapse identical functions/lambdas defined on different lines.
*/
unsigned char *co_cell2arg; /* Maps cell vars which are arguments. */
PyObject *co_filename; /* unicode (where it was loaded from) */ // 文件名
PyObject *co_name; /* unicode (name, for reference) */
int co_firstlineno; /* first source line number */ // 第一行源码行数
PyObject *co_lnotab; /* string (encoding addr<->lineno 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;
至此,可知脚本编译生成的字节码都存放在co_code中,其他属性字段都是存放了code在编译过程中保存的相关信息,这就是这个code对象在字节码解释器中运行时需要的局部变量,变量名等都是从code的相关字段中取值,有了PyCodeObject对象后,是如何在Python中被执行的呢?此时还需要将PyCodeObject对象转换成PyFrameObject对象。
Python的frame对象
在Python字节码执行的一级,会将PyCodeObject对象包装成PyFrameObject,一个PyCodeObject对应一个PyFrameObject对象,由此可知,test.py中存在三个PyFrameObject对象,除了show和foo外,还有一个是整个脚本的字节码所对应的PyFrameObject对象。
typedef struct _frame {
PyObject_VAR_HEAD
struct _frame *f_back; /* previous frame, or NULL */ // 上一个栈帧对象
PyCodeObject *f_code; /* code segment */ // 对应的code对象
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; // 栈顶
...
int f_lasti; /* Last instruction if called */ // 上一条执行命令
/* Call PyFrame_GetLineNumber() instead of reading this field
directly. As of 2.3 f_lineno is only valid when tracing is
active (i.e. when f_trace is set). At other times we use
PyCode_Addr2Line to calculate the line from the current
bytecode index. */
int f_lineno; /* Current line number */ // 当前行数
int f_iblock; /* index in f_blockstack */
char f_executing; /* whether the frame is still executing */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */ // 动态内存
} PyFrameObject;
由此可知,多个code对象就对应多个PyFrameObject对象,每一个栈帧都是像函数调用那样嵌套调用执行,此时继续分析Frame对象是如何Python的运行环境中执行。
Python3运行时环境
Python在运行的初始化阶段,在_Py_InitializeEx_Private函数中初始化了interp和tstate;
interp = PyInterpreterState_New();
if (interp == NULL)
Py_FatalError("Py_Initialize: can't make first interpreter");
tstate = PyThreadState_New(interp);
if (tstate == NULL)
Py_FatalError("Py_Initialize: can't make first thread");
由此可知,interp对应的就是一个解释器对象,tstate对应在Python中就是一个线程对象,这两者是如何推动Python的执行呢?
typedef struct _is {
struct _is *next; // 下一个解释器对象
struct _ts *tstate_head; // 线程对象
PyObject *modules; // 模块的列表
PyObject *modules_by_index;
PyObject *sysdict; // 系统模块列表
PyObject *builtins; // 内建对象列表
PyObject *importlib; // 导入模块的函数
PyObject *codec_search_path;
PyObject *codec_search_cache;
PyObject *codec_error_registry;
int codecs_initialized;
int fscodec_initialized;
#ifdef HAVE_DLOPEN
int dlopenflags;
#endif
#ifdef WITH_TSC
int tscdump;
#endif
PyObject *builtins_copy; // 内建对象副本
} PyInterpreterState;
由PyInterpreterState定义可以看出,解释器对象提供基本的内建对象和模块的数据,再来查看PyThreadState对象的定义;
typedef struct _ts {
/* See Python/ceval.c for comments explaining most fields */
struct _ts *prev; // 上一个线程对象
struct _ts *next; // 下一个线程对象
PyInterpreterState *interp; // 依赖的解释器对象
struct _frame *frame; // 当前执行的帧
...
long thread_id; /* Thread id where this tstate was created */ // 线程id
...
} PyThreadState;
主要定义了上下线程对象,当前锁对应的解释器对象,和当前正在执行的栈帧。此时的栈帧就是将PyCodeObject生成的栈帧,在Python的启动阶段的时候会调用_PyEval_EvalCodeWithName函数,该函数中就是调用PyFrame_New函数生成新的执行栈帧;
static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,
PyObject *name, PyObject *qualname)
{
...
f = PyFrame_New(tstate, co, globals, locals);
...
retval = PyEval_EvalFrameEx(f,0);
...
}
此时就进入字节码解释器解释执行,当执行到另一个PyCodeObject对象时,就调用PyFrame_New生成一个对应的frame调用字节码解释器继续执行。该生成过程位于fast_function函数中,后文会对其进行详细分析。至此Python运行的大概原理与执行的方式基本分析完毕。
总结
Python的运行过程图可归纳如下所示,
本文只是简单的分析了启动过程和Python的一个大致的执行过程,其中细节或有疏漏错误的地方请批评指正,具体详细的过程如有兴趣可自行调试分析。