大师兄的Python源码学习笔记(三十三): 运行环境初始化(五
2021-09-17 本文已影响0人
superkmi
大师兄的Python源码学习笔记(三十二): 运行环境初始化(四)
大师兄的Python源码学习笔记(三十四): 模块的动态加载机制(一)
三、激活Python虚拟机
- 激活虚拟机是运行环境初始化的最后一步。
- 通常Python有两种运行方式:
- 在命令行下的交互式环境。
- 运行脚本文件。
- 虽然这两种方式看起来不同,但实际上殊途同归,最终将进入同一个字节码虚拟机。
- Python在初始化配置成功完成后,将最终调用PyRun_AnyFileExFlags:
Modules\main.c
int
Py_Main(int argc, wchar_t **argv)
{
_PyMain pymain = _PyMain_INIT;
pymain.use_bytes_argv = 0;
pymain.argc = argc;
pymain.wchar_argv = argv;
return pymain_main(&pymain);
}
Modules\main.c
static int
pymain_main(_PyMain *pymain)
{
int res = pymain_init(pymain);
if (res == 1) {
goto done;
}
pymain_run_python(pymain);
if (Py_FinalizeEx() < 0) {
/* Value unlikely to be confused with a non-error exit status or
other special meaning */
pymain->status = 120;
}
done:
pymain_free(pymain);
return pymain->status;
}
Modules\main.c
static void
pymain_run_python(_PyMain *pymain)
{
PyCompilerFlags cf = {.cf_flags = 0};
pymain_header(pymain);
pymain_import_readline(pymain);
if (pymain->command) {
pymain->status = pymain_run_command(pymain->command, &cf);
}
else if (pymain->module) {
pymain->status = (pymain_run_module(pymain->module, 1) != 0);
}
else {
pymain_run_filename(pymain, &cf);
}
pymain_repl(pymain, &cf);
}
Modules\main.c
pymain_run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
{
PyObject *unicode, *bytes = NULL;
const char *filename_str;
int run;
/* call pending calls like signal handlers (SIGINT) */
if (Py_MakePendingCalls() == -1) {
PyErr_Print();
return 1;
}
if (filename) {
unicode = PyUnicode_FromWideChar(filename, wcslen(filename));
if (unicode != NULL) {
bytes = PyUnicode_EncodeFSDefault(unicode);
Py_DECREF(unicode);
}
if (bytes != NULL) {
filename_str = PyBytes_AsString(bytes);
}
else {
PyErr_Clear();
filename_str = "<encoding error>";
}
}
else {
filename_str = "<stdin>";
}
run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf);
Py_XDECREF(bytes);
return run != 0;
}
Modules\main.c
/* Parse input from a file and execute it */
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);
}
- 在这里,会判断到底用哪种方式激活虚拟机,如果是文件模式,fp是文件的stdin, filename就是文件名;否则, fp指向的是系统的标准输入流。
1. 交互式运行方式
- 如果在PyRun_AnyFileExFlags中通过Py_FdIsInteractive判断fp是系统的标准输入流,则进入PyRun_InteractiveLoopFlags:
Python\pylifecycle.c
/*
* The file descriptor fd is considered ``interactive'' if either
* a) isatty(fd) is TRUE, or
* b) the -i flag was given, and the filename associated with
* the descriptor is NULL or "<stdin>" or "???".
*/
int
Py_FdIsInteractive(FILE *fp, const char *filename)
{
if (isatty((int)fileno(fp)))
return 1;
if (!Py_InteractiveFlag)
return 0;
return (filename == NULL) ||
(strcmp(filename, "<stdin>") == 0) ||
(strcmp(filename, "???") == 0);
}
Python\pylifecycle.c
int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
PyObject *filename, *v;
int ret, err;
PyCompilerFlags local_flags;
int nomem_count = 0;
#ifdef Py_REF_DEBUG
int show_ref_count = PyThreadState_GET()->interp->core_config.show_ref_count;
#endif
filename = PyUnicode_DecodeFSDefault(filename_str);
if (filename == NULL) {
PyErr_Print();
return -1;
}
if (flags == NULL) {
flags = &local_flags;
local_flags.cf_flags = 0;
}
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 {
ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
if (ret == -1 && PyErr_Occurred()) {
/* Prevent an endless loop after multiple consecutive MemoryErrors
* while still allowing an interactive command to fail with a
* MemoryError. */
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;
}
- 在这段代码中,创建了交互式环境提示符>>>和...
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);
}
- 随后进入了交互式环境:
do {
ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
if (ret == -1 && PyErr_Occurred()) {
/* Prevent an endless loop after multiple consecutive MemoryErrors
* while still allowing an interactive command to fail with a
* MemoryError. */
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);
- 在交互环境中,通过PyRun_InteractiveOneObjectEx对用户输入的Python语句进行编译和执行:
Python\pythonrun.c
/* A PyRun_InteractiveOneObject() auxiliary function that does not print the
* error on failure. */
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) {
/* Fetch encoding from sys.stdin if possible. */
v = _PySys_GetObjectId(&PyId_stdin);
if (v && v != Py_None) {
oenc = _PyObject_GetAttrId(v, &PyId_encoding);
if (oenc)
enc = PyUnicode_AsUTF8(oenc);
if (!enc)
PyErr_Clear();
}
}
v = _PySys_GetObjectId(&PyId_ps1);
if (v != NULL) {
v = PyObject_Str(v);
if (v == NULL)
PyErr_Clear();
else if (PyUnicode_Check(v)) {
ps1 = PyUnicode_AsUTF8(v);
if (ps1 == NULL) {
PyErr_Clear();
ps1 = "";
}
}
}
w = _PySys_GetObjectId(&PyId_ps2);
if (w != NULL) {
w = PyObject_Str(w);
if (w == NULL)
PyErr_Clear();
else if (PyUnicode_Check(w)) {
ps2 = PyUnicode_AsUTF8(w);
if (ps2 == NULL) {
PyErr_Clear();
ps2 = "";
}
}
}
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;
}
m = PyImport_AddModuleObject(mod_name);
if (m == NULL) {
PyArena_Free(arena);
return -1;
}
d = PyModule_GetDict(m);
v = run_mod(mod, filename, d, d, flags, arena);
PyArena_Free(arena);
if (v == NULL) {
return -1;
}
Py_DECREF(v);
flush_io();
return 0;
}
- 首先编译用户在交互式环境下输入的Python语句,结果是构造与Python语句对应的抽象语法树(AST),并返回AST:
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);
- 接着获得__main__ module中维护的dict:
m = PyImport_AddModuleObject(mod_name);
- 最后将m作为参数传递给run_mod执行用户输入的Python语句。
- 这个参数关系重大,实际上这里的参数d就将作为Python虚拟机开始执行时,当前活动的frame对象的local名字空间和global名字空间。
d = PyModule_GetDict(m);
v = run_mod(mod, filename, d, d, flags, arena);
2. 脚本文件运行方式
- 如果在PyRun_AnyFileExFlags中通过Py_FdIsInteractive判断fp是文件stdin,则进入PyRun_SimpleFileExFlags:
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;
m = PyImport_AddModule("__main__");
if (m == NULL)
return -1;
Py_INCREF(m);
d = PyModule_GetDict(m);
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);
if (maybe_pyc_file(fp, filename, ext, closeit)) {
FILE *pyc_fp;
/* Try to run a pyc file. First, re-open in binary */
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 {
/* When running from stdin, leave __main__.__loader__ alone */
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);
}
flush_io();
if (v == NULL) {
Py_CLEAR(m);
PyErr_Print();
goto done;
}
Py_DECREF(v);
ret = 0;
done:
if (set_file_name && PyDict_DelItemString(d, "__file__"))
PyErr_Clear();
Py_XDECREF(m);
return ret;
}
- 这里首先在__main__ module中设置__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;
}
- 接着通过PyRun_FileExFlags编译、执行脚本文件:
Python\pythonrun.c
PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
PyObject *locals, int closeit, PyCompilerFlags *flags)
{
PyObject *ret = NULL;
mod_ty mod;
PyArena *arena = NULL;
PyObject *filename;
filename = PyUnicode_DecodeFSDefault(filename_str);
if (filename == NULL)
goto exit;
arena = PyArena_New();
if (arena == NULL)
goto exit;
mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
flags, NULL, arena);
if (closeit)
fclose(fp);
if (mod == NULL) {
goto exit;
}
ret = run_mod(mod, filename, globals, locals, flags, arena);
exit:
Py_XDECREF(filename);
if (arena != NULL)
PyArena_Free(arena);
return ret;
}
- 可以看出,虽然交互式执行方式和脚本式执行方式在PyRun_AnyFileExFlags中分流,但他们的有相似的动作,最终都是进入run_mod。
3. 启动虚拟机
- 从run_mod开始,Python开始执行最后一项工作,启动字节码虚拟机:
Python\pythonrun.c
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
PyCompilerFlags *flags, PyArena *arena)
{
PyCodeObject *co;
PyObject *v;
co = PyAST_CompileObject(mod, filename, flags, -1, arena);
if (co == NULL)
return NULL;
v = PyEval_EvalCode((PyObject*)co, globals, locals);
Py_DECREF(co);
return v;
}
- 在这里,run_mod首先接过传入的AST,并转入PyAST_CompileObject,它将基于AST完成字节码编译工作,并创建一个PyCodeObject对象。
- 接着,开始通过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);
}
Python\ceval.c
PyObject *
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject *const *args, int argcount,
PyObject *const *kws, int kwcount,
PyObject *const *defs, int defcount,
PyObject *kwdefs, PyObject *closure)
{
return _PyEval_EvalCodeWithName(_co, globals, locals,
args, argcount,
kws, kws != NULL ? kws + 1 : NULL,
kwcount, 2,
defs, defcount,
kwdefs, closure,
NULL, NULL);
}
Python\ceval.c
/* This is gonna seem *real weird*, but if you put some other code between
PyEval_EvalFrame() and _PyEval_EvalFrameDefault() you will need to adjust
the test in the if statements in Misc/gdbinit (pystack and pystackv). */
PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject *const *args, Py_ssize_t argcount,
PyObject *const *kwnames, PyObject *const *kwargs,
Py_ssize_t kwcount, int kwstep,
PyObject *const *defs, Py_ssize_t defcount,
PyObject *kwdefs, PyObject *closure,
PyObject *name, PyObject *qualname)
{
PyCodeObject* co = (PyCodeObject*)_co;
PyFrameObject *f;
PyObject *retval = NULL;
PyObject **fastlocals, **freevars;
PyThreadState *tstate;
PyObject *x, *u;
const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
Py_ssize_t i, n;
PyObject *kwdict;
... ...
f = _PyFrame_New_NoTrack(tstate, co, globals, locals);
... ...
fastlocals = f->f_localsplus;
... ...
retval = PyEval_EvalFrameEx(f,0);
... ...
return retval;
}
- 到这里,终于见到了再熟悉不过的PyEval_EvalCodeEx和PyFrameObject对象,Python字节码虚拟机被唤醒。