楔子
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 函数。
// Python/pythonrun.c
int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
// ...
// 创建交互式提示符,sys.ps1
v = _PySys_GetObjectId(&PyId_ps1);
if (v == NULL) {
_PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));
Py_XDECREF(v);
}
// 同理这个也是一样,sys.ps2
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;
}
#ifdef Py_REF_DEBUG
if (show_ref_count) {
_PyDebug_PrintTotalRefs();
}
#endif
} 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) {
// ...
}
arena = PyArena_New();
if (arena == NULL) {
Py_XDECREF(v);
Py_XDECREF(w);
Py_XDECREF(oenc);
return -1;
}
// 编译用户在交互式环境下输入的 Python 语句,生成抽象语法树
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 函数。
脚本文件运行方式
然后是脚本文件运行方式。
// Python/pythonrun.c
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
{
PyObject *filename_obj = PyUnicode_DecodeFSDefault(filename);
if (filename_obj == NULL) {
return -1;
}
// 调用了 pyrun_simple_file
int res = pyrun_simple_file(fp, filename_obj, closeit, flags);
Py_DECREF(filename_obj);
return res;
}
static int
pyrun_simple_file(FILE *fp, PyObject *filename, int closeit,
PyCompilerFlags *flags)
{
PyObject *m, *d, *v;
int set_file_name = 0, ret = -1;
// __main__ 就是当前文件
m = PyImport_AddModule("__main__");
if (m == NULL)
return -1;
Py_INCREF(m);
// 模块的属性字典,同时也作为 local 空间和 global 空间
d = PyModule_GetDict(m);
// 在 __main__ 中设置 __file__ 属性
if (PyDict_GetItemString(d, "__file__") == NULL) {
if (PyDict_SetItemString(d, "__file__", filename) < 0) {
goto done;
}
if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) {
goto done;
}
set_file_name = 1;
}
int pyc = maybe_pyc_file(fp, filename, closeit);
if (pyc < 0) {
goto done;
}
// 如果是 pyc,那么以二进制模式打开
if (pyc) {
FILE *pyc_fp;
/* Try to run a pyc file. First, re-open in binary */
if (closeit) {
fclose(fp);
}
pyc_fp = _Py_fopen_obj(filename, "rb");
if (pyc_fp == 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, d, d, flags);
} else {
if (PyUnicode_CompareWithASCIIString(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_file(fp, filename, Py_file_input, d, d,
closeit, flags);
}
// ...
return ret;
}
static PyObject *
pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals,
PyObject *locals, int closeit, PyCompilerFlags *flags)
{
PyArena *arena = PyArena_New();
if (arena == NULL) {
return NULL;
}
mod_ty mod;
// 编译文件
mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
flags, NULL, arena);
if (closeit) {
fclose(fp);
}
PyObject *ret;
if (mod != NULL) {
// 执行,依旧是调用了 run_mod
ret = run_mod(mod, filename, globals, locals, flags, arena);
}
else {
ret = NULL;
}
PyArena_Free(arena);
return ret;
}
很显然,脚本文件和交互式之间的执行流程是不同的,但最终都进入了 run_mod,而且同样将 __main__ 中维护的 PyDictObject 对象作为 local 名字空间和 global 名字空间传给了 run_mod。
启动虚拟机
前面的都是准备工作,到这里才算是真正开始启动虚拟机。
// Python/pythonrun.c
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 对象。此时,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_EvalFrameDefault,然后进入那个拥有巨型 switch 的 for 循环,不停地执行字节码指令,而运行时栈就是参数的容身之所。
所以整个流程就是先创建进程,进程创建线程,设置 builtins(包括设置 __name__、内置对象、内置函数等等)、设置缓存池,然后各种初始化,设置搜索路径。最后分词、编译、激活虚拟机执行。而执行方式就是调用曾经与我们朝夕相处的帧评估函数 ,掌控 Python 世界中无数对象的生生灭灭。参数 f 就是 PyFrameObject 对象,我们曾经探索了很久,现在一下子就回到了当初,有种梦回栈帧对象的感觉。
目前的话,Python 的骨架我们已经看清了,虽然还有很多细节隐藏在幕后,至少神秘的面纱已经被撤掉了。
小结
当我们在控制台输入 python 的那一刻,背后真的是做了大量的工作。因为 Python 是动态语言,很多操作都要发生在运行时。关于运行时环境的初始化和虚拟机的启动就说到这里,接下来我们就要介绍 Python 的多线程了,以及被称为万恶之源的 GIL。
欢迎大家关注我的公众号:古明地觉的编程教室。
如果觉得文章对你有所帮助,也可以请作者吃个馒头,Thanks♪(・ω・)ノ。