楔子
Python 的运行方式有两种,一种是在命令行中输入 python 进入交互式环境;另一种则是以 python xxx.py 的方式运行脚本文件。尽管方式不同,但最终殊途同归,进入相同的处理逻辑。
而 Python 在初始化(Py_Initialize)完成之后,会执行 pymain_run_file。
//Modules/main.c
static int
pymain_run_file(PyConfig *config, PyCompilerFlags *cf)
{
//获取文件名
const wchar_t *filename = config->run_filename;
if (PySys_Audit("cpython.run_file", "u", filename) < 0) {
return pymain_exit_err_print();
}
//打开文件
FILE *fp = _Py_wfopen(filename, L"rb");
//如果fp为NULL, 证明文件打开失败
if (fp == NULL) {
char *cfilename_buffer;
const char *cfilename;
int err = errno;
cfilename_buffer = _Py_EncodeLocaleRaw(filename, NULL);
if (cfilename_buffer != NULL)
cfilename = cfilename_buffer;
else
cfilename = "<unprintable file name>";
fprintf(stderr, "%ls: can't open file '%s': [Errno %d] %s\n",
config->program_name, cfilename, err, strerror(err));
PyMem_RawFree(cfilename_buffer);
return 2;
}
//......
//调用PyRun_AnyFileExFlags
int run = PyRun_AnyFileExFlags(fp, filename_str, 1, cf);
Py_XDECREF(bytes);
return (run != 0);
}
//Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
{
if (filename == NULL)
filename = "???";
//根据fp是否代表交互环境,对程序进行流程控制
if (Py_FdIsInteractive(fp, filename)) {
//如果是交互环境,调用PyRun_InteractiveLoopFlags
int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
if (closeit)
fclose(fp);
return err;
}
else
//否则说明是一个普通的python脚本
//执行PyRun_SimpleFileExFlags
return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);
}
我们看到交互式和执行 py 脚本方式调用的是两个不同的函数,但是别着急,最终你会看到它们又分久必合、走到一起。
交互式环境
看看交互式运行时候的情形,不过在此之前先来看一下提示符。
>>> name = "satori"
>>> if name == "satori":
... pass
...
>>> import sys
>>> sys.ps1 = "+++ "
+++
+++ sys.ps2 = "--- "
+++
+++ if name == "satori":
--- pass
---
+++
我们每输入一行,开头都是 >>>,这个是 sys.ps1;而输入语句块,没输入完的时候,那么显示 ...,这个是 sys.ps2。而这两者都支持修改,如果修改了,那么就是我们自己定义的了。
交互式环境会执行 PyRun_InteractiveLoopFlags 函数。
int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
//....
//创建交互式提示符
v = _PySys_GetObjectId(&PyId_ps1);
if (v == NULL) {
_PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));
Py_XDECREF(v);
}
//同理这个也是一样
v = _PySys_GetObjectId(&PyId_ps2);
if (v == NULL) {
_PySys_SetObjectId(&PyId_ps2, v = PyUnicode_FromString("... "));
Py_XDECREF(v);
}
err = 0;
do {
//这里就进入了交互式环境
//我们看到每次都调用了PyRun_InteractiveOneObjectEx
//直到下面的ret != E_EOF不成立,停止循环
//一般情况就是我们输入exit()退出了
ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
if (ret == -1 && PyErr_Occurred()) {
if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
if (++nomem_count > 16) {
PyErr_Clear();
err = -1;
break;
}
} else {
nomem_count = 0;
}
PyErr_Print();
flush_io();
} else {
nomem_count = 0;
}
//......
} while (ret != E_EOF);
Py_DECREF(filename);
return err;
}
static int
PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
PyCompilerFlags *flags)
{
PyObject *m, *d, *v, *w, *oenc = NULL, *mod_name;
mod_ty mod;
PyArena *arena;
const char *ps1 = "", *ps2 = "", *enc = NULL;
int errcode = 0;
_Py_IDENTIFIER(encoding);
_Py_IDENTIFIER(__main__);
mod_name = _PyUnicode_FromId(&PyId___main__); /* borrowed */
if (mod_name == NULL) {
return -1;
}
if (fp == stdin) {
//......
}
v = _PySys_GetObjectId(&PyId_ps1);
if (v != NULL) {
//......
}
w = _PySys_GetObjectId(&PyId_ps2);
if (w != NULL) {
//.....
}
//编译用户在交互式环境下输入的python语句
arena = PyArena_New();
if (arena == NULL) {
Py_XDECREF(v);
Py_XDECREF(w);
Py_XDECREF(oenc);
return -1;
}
//生成抽象语法树
mod = PyParser_ASTFromFileObject(fp, filename, enc,
Py_single_input, ps1, ps2,
flags, &errcode, arena);
Py_XDECREF(v);
Py_XDECREF(w);
Py_XDECREF(oenc);
if (mod == NULL) {
PyArena_Free(arena);
if (errcode == E_EOF) {
PyErr_Clear();
return E_EOF;
}
return -1;
}
//获取<module __main__>中维护的dict
m = PyImport_AddModuleObject(mod_name);
if (m == NULL) {
PyArena_Free(arena);
return -1;
}
d = PyModule_GetDict(m);
//执行用户输入的python语句
v = run_mod(mod, filename, d, d, flags, arena);
PyArena_Free(arena);
if (v == NULL) {
return -1;
}
Py_DECREF(v);
flush_io();
return 0;
}
在run_mod之前,Python 会将 main 中维护的 PyDictObject 对象取出,作为参数传递给 run_mod 函数,这个参数关系极为重要,实际上代码中的参数 d 就将作为虚拟机开始执行时、当前活动 frame 对象的 local 空间和 global 空间。
脚本文件运行方式
然后是脚本文件运行方式。
//.include/compile.h
#define Py_file_input 257
//Python/pythonrun.c
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
{
PyObject *m, *d, *v;
const char *ext;
int set_file_name = 0, ret = -1;
size_t len;
//__main__就是当前文件
m = PyImport_AddModule("__main__");
if (m == NULL)
return -1;
Py_INCREF(m);
//还记得这个d吗?
//当前活动frame对象的local和global名字空间
d = PyModule_GetDict(m);
//在__main__中设置__file__属性
if (PyDict_GetItemString(d, "__file__") == NULL) {
PyObject *f;
f = PyUnicode_DecodeFSDefault(filename);
if (f == NULL)
goto done;
if (PyDict_SetItemString(d, "__file__", f) < 0) {
Py_DECREF(f);
goto done;
}
if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) {
Py_DECREF(f);
goto done;
}
set_file_name = 1;
Py_DECREF(f);
}
len = strlen(filename);
ext = filename + len - (len > 4 ? 4 : 0);
//如果是pyc
if (maybe_pyc_file(fp, filename, ext, closeit)) {
FILE *pyc_fp;
//二进制模式打开
if (closeit)
fclose(fp);
if ((pyc_fp = _Py_fopen(filename, "rb")) == NULL) {
fprintf(stderr, "python: Can't reopen .pyc file\n");
goto done;
}
if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__\n");
ret = -1;
fclose(pyc_fp);
goto done;
}
v = run_pyc_file(pyc_fp, filename, d, d, flags);
} else {
if (strcmp(filename, "<stdin>") != 0 &&
set_main_loader(d, filename, "SourceFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__\n");
ret = -1;
goto done;
}
//执行脚本文件
v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
closeit, flags);
}
//.......
}
PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
PyObject *locals, int closeit, PyCompilerFlags *flags)
{
PyObject *ret = NULL;
//......
//编译
mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
flags, NULL, arena);
if (closeit)
fclose(fp);
if (mod == NULL) {
goto exit;
}
//执行, 依旧是调用了runmod
ret = run_mod(mod, filename, globals, locals, flags, arena);
exit:
Py_XDECREF(filename);
if (arena != NULL)
PyArena_Free(arena);
return ret;
}
很显然,脚本文件和交互式之间的执行流程是不同的,但最终都进入了 run_mod,而且同样将 main 中维护的 PyDictObject对象作为 local名字空间和 global名字空间传入了 run_mod。
启动虚拟机
前面的都是准备工作,到这里才算是真正开始启动虚拟机。
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
PyCompilerFlags *flags, PyArena *arena)
{
PyCodeObject *co;
PyObject *v;
//基于ast编译字节码指令序列,创建PyCodeObject对象
co = PyAST_CompileObject(mod, filename, flags, -1, arena);
if (co == NULL)
return NULL;
if (PySys_Audit("exec", "O", co) < 0) {
Py_DECREF(co);
return NULL;
}
//创建PyFrameObject,执行PyCodeObject对象中的字节码指令序列
v = run_eval_code_obj(co, globals, locals);
Py_DECREF(co);
return v;
}
run_mod 接手传来的 ast,然后再传到 PyAST_CompileObject 中,创建了一个我们已经非常熟悉的 PyCodeObject对象。
关于这个完整的编译过程,就又是另一个话题了,总之先是 Scanner 进行词法分析、将源代码切分成一个个的 token,然后 Parser 在词法分析的结果之上进行语法分析、根据切分好的 token 生成抽象语法树(AST,abstract syntax tree),然后将 AST 编译 PyCodeObject 对象,最后再由虚拟机执行。
整个流程就是这样,至于到底是怎么分词、怎么建立语法树的,这就涉及到编译原理了,个人觉得甚至比研究Python虚拟机还难。有兴趣的话可以去看源码中的 Parser 目录,如果能把 Python 的分词、语法树的建立给了解清楚,那我觉得你完全可以手写一个正则表达式的引擎、以及各种模板语言。
此时,Python 已经做好一切工作,于是开始通过 run_eval_code_obj 着手唤醒虚拟机。
static PyObject *
run_eval_code_obj(PyCodeObject *co, PyObject *globals, PyObject *locals)
{
PyObject *v;
//......
v = PyEval_EvalCode((PyObject*)co, globals, locals);
if (!v && PyErr_Occurred() == PyExc_KeyboardInterrupt) {
_Py_UnhandledKeyboardInterrupt = 1;
}
return v;
}
函数中调用了 PyEval_EvalCode,根据前面的介绍,我们知道最终一定会走到 PyEval_EvalFrameEx。最终调用 _PyEval_EvalFrameDefault,然后进入那个拥有巨型 switch 的 for 循环,不停地执行字节码指令,而运行时栈就是参数的容身之所。
所以整个流程就是先创建进程,进程创建线程,设置 builtins(包括设置__name__、内建对象、内置函数方法等等)、设置缓存池,然后各种初始化,设置搜索路径。最后分词、编译、激活虚拟机执行。
而执行方式就是调用曾经与我们朝夕相处的 PyEval_EvalFrameEx ,掌控 Python 世界中无数对象的生生灭灭。参数 f 就是 PyFrameObject 对象,我们曾经探索了很久,现在一下子就回到了当初,有种梦回栈帧对象的感觉。
目前的话,Python 的骨架我们已经看清了,虽然还有很多细节隐藏在幕后,至少神秘的面纱已经被撤掉了。
小结
当我们在控制台输入 python 的那一刻,背后真的是做了大量的工作。因为Python是动态语言,很多操作都要发生在运行时。
关于运行时环境的初始化和虚拟机的启动就说到这里,接下来我们就要介绍 Python 的多线程了,以及被称为万恶之源的 GIL。
以上就是本次分享的所有内容,想要了解更多欢迎前往公众号:Python 编程学习圈,每日干货分享