楔子

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♪(・ω・)ノ。