楔子

我们之前完成了 Python 的字节码、以及虚拟机的剖析工作,但这仅仅只是一部分,而其余的部分则被遮在了幕后。记得在分析虚拟机的时候,曾这么说过:

解释器启动时,首先会进行 "运行时环境" 的初始化,关于 "运行时环境" 的初始化是一个非常复杂的过程。并且 "运行时环境" 和 "执行环境" 是不同的,"运行时环境" 是一个全局的概念,而 "执行环境" 是一个栈帧。关于 "运行时环境" 后面会单独分析,这里就假设初始化动作已经完成,我们已经站在了虚拟机的门槛外面,只需要轻轻推动第一张骨牌,整个执行过程就会像多米诺骨牌一样,一环扣一环地展开。

所以这次,我们将回到时间的起点,从 Python 的应用程序被执行开始,一步一步紧紧跟随 Python 的轨迹,完整地展示解释器在启动之初的所有动作。当我们了解所有的初始化动作之后,也就能对 Python 引擎执行字节码指令时的整个运行环境了如指掌了。

线程模型

我们知道线程是操作系统调度的最小单元,那么 Python 的线程又是怎样的呢?

启动一个 Python 线程,底层会启动一个 C 线程,然后启动操作系统的一个原生线程(OS 线程)。所以 Python 线程实际上是对 OS 线程的一个封装,因此 Python 的线程是货真价实的。

然后 Python 还提供了一个 PyThreadState 对象,也就是线程状态对象,维护 OS 线程执行的状态信息,相当于是 OS 线程的一个抽象描述。虽然真正用来执行的线程及其状态肯定是由操作系统进行维护的,但 Python 虚拟机在运行的时候总需要另外一些与线程相关的状态和信息,比如是否发生了异常等等,这些信息显然操作系统是没有办法提供的。

而 PyThreadState 对象正是为 OS 线程准备的、在虚拟机层面保存其状态信息的对象,也就是线程状态对象。在 Python 中,当前活动的 OS 线程对应的 PyThreadState 对象可以通过调用 PyThreadState_GET 函数获得,有了线程状态对象之后,就可以设置一些额外信息了。具体内容,我们后面会说。

当然除了线程状态对象之外,还有进程状态对象,我们来看看两者在底层的定义是什么?它们位于 Include/pystate.h 中。

// ts 是 thread state 的简写
typedef struct _ts PyThreadState;
// is 是 interpreter state 的简写
typedef struct _is PyInterpreterState;

里面的 PyThreadState 表示线程状态对象,PyInterpreterState 表示进程状态对象,但它们都是 typedef 起的一个别名。前者是 struct _ts 的别名,后者是 struct _is 的别名,来看一下它们长什么样。

// Include/cpython/pystate.h

// Python 的异常信息有三个属性
// exc_type:异常类型;exc_value:异常值,说白了就是异常本身;exc_traceback:异常的回溯栈
// 而 _PyErr_StackItem 相当于对这三个属性做了封装,并且还通过 previous_item 形成了一个链表
typedef struct _err_stackitem {
    PyObject *exc_type, *exc_value, *exc_traceback;
    struct _err_stackitem *previous_item;
} _PyErr_StackItem;

struct _ts {
    // 指向上一个线程状态对象
    struct _ts *prev;
    // 指向下一个线程状态对象    
    struct _ts *next;
    // 进程状态对象,因为每个线程都隶属于一个进程
    PyInterpreterState *interp;
    // 当前正在执行的栈桢
    struct _frame *frame;
    // 递归深度
    int recursion_depth;
    // 标记栈是否溢出,如果溢出,还允许 50 次调用来处理运行时错误
    char overflowed;
    // 标记当前调用不能导致栈溢出的标志位
    char recursion_critical;
    // 栈检查计数器
    int stackcheck_counter;
    // 追踪和性能分析时的执行深度计数器
    int tracing;
    // 是否启用追踪
    int use_tracing;
    // C 级性能分析的函数指针
    Py_tracefunc c_profilefunc;
    // C 级追踪函数指针
    Py_tracefunc c_tracefunc;
    // Python 级性能分析对象
    PyObject *c_profileobj;
    // Python 级追踪对象
    PyObject *c_traceobj;
    // 当前抛出的异常信息
    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;
    // 当前正在处理的异常状态(如果没有协程/生成器)
    _PyErr_StackItem exc_state;
    // 指向当前正在处理的异常栈的栈顶
    // 估计有人好奇 exc_state 和 exc_info 啥区别,我们稍后说
    _PyErr_StackItem *exc_info;
    // 存储线程状态信息的字典
    PyObject *dict;
    // GIL 状态计数器
    int gilstate_counter;
    // 待抛出的异步异常
    PyObject *async_exc;
    // 创建该线程状态对象的线程 ID
    unsigned long thread_id;
    // 嵌套层级,用于延迟删除
    int trash_delete_nesting;
    // 延迟删除的对象,关于延迟删除,我们介绍列表的时候说过
    PyObject *trash_delete_later;
    // 线程状态正常删除时的回调函数
    void (*on_delete)(void *);
    // 回调函数的数据参数(实现为一个指向锁的弱引用)
    void *on_delete_data;
    // 协程起源追踪深度
    int coroutine_origin_tracking_depth;
    // 当异步生成器首次迭代时,用于存储相关状态和处理异常
    PyObject *async_gen_firstiter;
    // 当异步生成器被垃圾回收时,用于执行必要的清理工作
    PyObject *async_gen_finalizer;
    // 上下文对象
    PyObject *context;
    // 上下文版本号
    uint64_t context_ver;
    // 线程状态对象的唯一标识符
    uint64_t id;
};

以上是线程状态对象,然后我们再来看看进程状态对象。需要补充的是,struct _is、或者说 PyInterpreterState 在严格意义上应该叫做解释器状态对象。我们知道当解释器启动后会创建一个进程,但这两者并不是一对一的,因为 CPython 支持多解释器模式。也就是说可以在一个进程中启动多个解释器,这种模式一般应用在嵌入式 Python 或需要隔离环境的情况。

但是多解释器模式只能通过手动调用 Python/C API 实现,Python 代码层面无法直接创建和管理多个解释器,所以默认一个进程只会对应一个解释器实例。因此为了和线程对应,我们这里称 PyInterpreterState 为进程状态对象。

// Include/internal/pycore_pystate.h

struct _is {
    // Python 支持多进程,多个进程状态对象也会以链表的形式进行组织
    // next 字段会指向下一个进程状态对象
    struct _is *next;
    // 每个进程内部可以有很多个线程,那么自然就会有很多个线程状态对象
    // 这些线程状态对象会以链表的形式串起来,而 tstate_head 指向链表的头节点
    struct _ts *tstate_head;
    // 进程状态对象的 ID
    int64_t id;
    // 进程状态对象的引用计数
    int64_t id_refcount;
    // 指示解释器是否需要引用计数跟踪
    // 当多个子解释器共享同一个 ID 时,此标志用于确保 ID 的正确管理和清理
    int requires_idref;
    // ID 相关的互斥锁
    PyThread_type_lock id_mutex;
    // 终止标志
    int finalizing;
    // sys.modules,所以可以看出,多个线程共用一个 sys.modules
    PyObject *modules;
    // 按模块索引存储的缓存,它允许通过数字索引快速访问模块
    // 而不是每次都通过模块名在 modules 字典中查找,提高了模块访问性能
    PyObject *modules_by_index;
    // sys 模块的属性字典
    PyObject *sysdict;
    // 内置名称空间
    PyObject *builtins;
    // 导入机制
    PyObject *importlib;
    // 线程切换检查间隔
    int check_interval;
    // 进程内部的线程数量
    long num_threads;
    // 线程栈大小
    size_t pythread_stacksize;
    // 编解码器搜索路径
    PyObject *codec_search_path;
    // 编解码器缓存
    PyObject *codec_search_cache;
    // 存储自定义的编解码错误处理器
    // 允许通过 codecs.register_error() 注册新的错误处理策略,用于处理编解码过程中遇到的错误
    PyObject *codec_error_registry;
    // 表示编解码系统是否已经完成初始化,为 1 时表示已初始化,0 表示未初始化
    int codecs_initialized;
    // 文件系统编码设置
    struct {
        char *encoding;   /* Filesystem encoding (encoded to UTF-8) */
        char *errors;     /* Filesystem errors (encoded to UTF-8) */
        _Py_error_handler error_handler;
    } fs_codec;
    // 解释器的配置参数结构体
    // 包含:路径设置、命令行参数、环境变量设置、编码设置、性能和调试选项、内存分配器设置、隔离选项等
    PyConfig config;
    // 控制动态库加载行为的标志位,用于 dlopen() 函数调用
    // 这个标志决定了动态库加载时的符号解析策略和可见性
#ifdef HAVE_DLOPEN
    int dlopenflags;
#endif
    // 存储进程状态信息的字典
    PyObject *dict;
    // 内置名字空间的备份副本,用于在必要时恢复到内置名字空间的默认状态
    // 这是一个安全措施,防止内置名字空间被意外修改。
    PyObject *builtins_copy;
    // 存储当前解释器使用的导入函数,通常是 __import__ 函数
    // 因此解释器也允许自定义模块导入行为,用于实现特殊的导入逻辑
    PyObject *import_func;
    // 帧评估函数,默认是 _PyEval_EvalFrameDefault
    // 所以解释器也支持自定义帧评估函数,即自定义字节码执行逻辑
    _PyFrameEvalFunction eval_frame;
        Py_ssize_t co_extra_user_count;
    freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];

#ifdef HAVE_FORK
    PyObject *before_forkers;
    PyObject *after_forkers_parent;
    PyObject *after_forkers_child;
#endif
    /* AtExit module */
    void (*pyexitfunc)(PyObject *);
    PyObject *pyexitmodule;

    uint64_t tstate_next_unique_id;

    struct _warnings_runtime_state warnings;

    PyObject *audit_hooks;
};

所以 PyInterpreterState 对象可以看成是对进程的模拟, PyThreadState 对象可以看成是对线程的模拟。我们之前分析虚拟机的时候说过执行环境,如果再将运行时环境加进去的话。

进程状态对象的 tstate_head 指向了线程状态对象,对应当前活跃的 Python 线程;而每个线程状态对象的 frame 都指向当前正在执行的栈帧对象。

线程环境的初始化

在解释器启动之后,初始化的动作是从 Py_NewInterpreter 函数开始的,然后这个函数调用了 new_interpreter 函数完成初始化。至于这两个函数长什么样一会儿再聊,先往后看。

我们知道当操作系统在运行一个可执行文件时,首先会创建一个进程内核。同理在 Python 中亦是如此,会在 new_interpreter 中调用 PyInterpreterState_New 创建一个崭新的 PyInterpreterState 对象,该函数位于 Python/pystate.c 中。

PyInterpreterState *
PyInterpreterState_New(void)
{
    // 触发审计事件
    if (PySys_Audit("cpython.PyInterpreterState_New", NULL) < 0) {
        return NULL;
    }
    // 为进程状态对象分配内存
    PyInterpreterState *interp = PyMem_RawMalloc(sizeof(PyInterpreterState));
    if (interp == NULL) {
        return NULL;
    }
    // 初始化内存
    memset(interp, 0, sizeof(*interp));
    interp->id_refcount = -1;
    // 每个线程执行 100 条字节码后进行切换
    interp->check_interval = 100;
    // 初始化 Python 配置
    PyConfig_InitPythonConfig(&interp->config);
    // 设置帧评估函数,默认为 _PyEval_EvalFrameDefault
    interp->eval_frame = _PyEval_EvalFrameDefault;
    // 设置动态库加载标志
#ifdef HAVE_DLOPEN
#if HAVE_DECL_RTLD_NOW
    interp->dlopenflags = RTLD_NOW;
#else
    interp->dlopenflags = RTLD_LAZY;
#endif
#endif
    // _PyRuntimeState 是 Python 解释器的全局运行时状态结构体,管理以下内容
    // GIL 锁相关的状态、解释器链表、垃圾回收系统状态、核心模块和类型
    // 信号处理、内存分配器、线程状态追踪、全局审计钩子等
    // 注:每个进程只有一个全局运行时(_PyRuntime),它是最高级的运行时状态容器
    _PyRuntimeState *runtime = &_PyRuntime;
    // struct pyinterpreters 内部有四个字段
    // PyThread_type_lock mutex:互斥锁
    // PyInterpreterState *head:进程状态对象的头结点
    // PyInterpreterState *main:主进程对应的进程状态对象
    // int64_t next_id:下一个可用的解释器 ID,注意:主解释器 ID、或者说主进程状态对象的 ID 永远是 0
    struct pyinterpreters *interpreters = &runtime->interpreters;
    
    // 获取全局解释器锁,用于保护解释器链表的操作
    // 它确保在多线程环境下,解释器的创建和管理是线程安全的
    HEAD_LOCK(runtime);
    // next_id 必须大于等于 0
    if (interpreters->next_id < 0) {
        PyErr_SetString(PyExc_RuntimeError,
                        "failed to get an interpreter ID");
        PyMem_RawFree(interp);
        interp = NULL;
    }
    else {
        // 分配新 ID 并更新 next_id
        interp->id = interpreters->next_id;
        interpreters->next_id += 1;
        // 让新创建的进程状态对象 interp,成为进程状态对象链表的头结点
        // 所以它的 next 要等于 interpreters->head
        interp->next = interpreters->head;
        // 如果 interpreters->main 等于 NULL,说明当前的进程是第一个进程
        // 那么显然它就是主进程,而之后创建的进程就是子进程了
        if (interpreters->main == NULL) {
            interpreters->main = interp;
        }
        // 然后再让 interpreters->head 等于 interp
        interpreters->head = interp;
    }
    HEAD_UNLOCK(runtime);

    if (interp == NULL) {
        return NULL;
    }

    interp->tstate_next_unique_id = 0;
    interp->audit_hooks = NULL;
    return interp;
}

所以解释器在启动时,会创建一个 PyInterpreterState 对象。如果开启了多进程,那么内部会继续创建,然后通过 next 指针将多个 PyInterpreterState 串成一个链表结构。

在调用 PyInterpreterState_New 成功创建 PyInterpreterState 之后,会再接再厉,调用 PyThreadState_New 创建一个全新的线程状态对象,相关函数定义同样位于 Python/pystate.c 中。

PyThreadState *
PyThreadState_New(PyInterpreterState *interp)
{
    return new_threadstate(interp, 1);
}

我们注意到这个函数接收一个 PyInterpreterState,这说明线程是依赖进程的,因为需要进程给自己分配资源,然后这个函数又调用了 new_threadstate。除了传递 PyInterpreterState 之外,还传了一个 1,想也不用想这肯定是创建的线程数量。这里创建 1 个,也就是主线程(main thread)。

static PyThreadState *
new_threadstate(PyInterpreterState *interp, int init)
{
    _PyRuntimeState *runtime = &_PyRuntime;
    // 为线程状态对象申请内存
    PyThreadState *tstate = (PyThreadState *)PyMem_RawMalloc(sizeof(PyThreadState));
    if (tstate == NULL) {
        return NULL;
    }
    // 设置从线程中获取函数调用栈的操作
    if (_PyThreadState_GetFrame == NULL) {
        _PyThreadState_GetFrame = threadstate_getframe;
    }
    // 下面就是设置内部的字段属性
    // 该线程所在的进程
    tstate->interp = interp;
    // 当前正在执行的栈桢,初始为 NULL
    tstate->frame = NULL;
    // 递归深度,初始为 0
    tstate->recursion_depth = 0;
    tstate->overflowed = 0;
    tstate->recursion_critical = 0;
    tstate->stackcheck_counter = 0;
    tstate->tracing = 0;
    tstate->use_tracing = 0;
    tstate->gilstate_counter = 0;
    tstate->async_exc = NULL;
    tstate->thread_id = PyThread_get_thread_ident();

    tstate->dict = NULL;

    tstate->curexc_type = NULL;
    tstate->curexc_value = NULL;
    tstate->curexc_traceback = NULL;

    tstate->exc_state.exc_type = NULL;
    tstate->exc_state.exc_value = NULL;
    tstate->exc_state.exc_traceback = NULL;
    tstate->exc_state.previous_item = NULL;
    tstate->exc_info = &tstate->exc_state;

    tstate->c_profilefunc = NULL;
    tstate->c_tracefunc = NULL;
    tstate->c_profileobj = NULL;
    tstate->c_traceobj = NULL;

    tstate->trash_delete_nesting = 0;
    tstate->trash_delete_later = NULL;
    tstate->on_delete = NULL;
    tstate->on_delete_data = NULL;

    tstate->coroutine_origin_tracking_depth = 0;

    tstate->async_gen_firstiter = NULL;
    tstate->async_gen_finalizer = NULL;

    tstate->context = NULL;
    tstate->context_ver = 1;

    if (init) {
        _PyThreadState_Init(runtime, tstate);
    }

    HEAD_LOCK(runtime);
    tstate->id = ++interp->tstate_next_unique_id;
    tstate->prev = NULL;
    // 当前线程状态对象的 next,我们看到指向了线程状态对象链表的头结点
    tstate->next = interp->tstate_head;
    // 因为每个线程状态对象的 prev 指针都要指向上一个线程状态对象
    // 如果是头结点的话,那么 prev 就是 NULL
    // 但由于新的线程状态对象在插入之后变成了链表的新的头结点
    // 因此还需要将插入之前的头结点的 prev 指向新插入的线程状态对象
    if (tstate->next)
        tstate->next->prev = tstate;
    // 将 tstate 设置为线程状态对象链表的新的头结点
    interp->tstate_head = tstate;
    HEAD_UNLOCK(runtime);
    // 返回线程状态对象
    return tstate;
}

和 PyInterpreterState_New 相同, PyThreadState_New 会申请内存,创建线程状态对象,并且对每个字段进行初始化。其中 prev 指针和 next 指针分别指向了上一个线程状态对象和下一个线程状态对象。而且也肯定会存在某一时刻,会有多个 PyThreadState 对象组成一个链表,那什么时刻会发生这种情况呢?显然用鼻子想也知道这是在启动多线程的时候。

Python 在插入线程状态对象的时候采用的是头插法。

从源码中我们看到,虚拟机设置了从线程中获取函数调用栈的操作,所谓函数调用栈就是前面说的 PyFrameObject 对象链表。而且在源码中,PyThreadState 关联了 PyInterpreterState,PyInterpreterState 也关联了 PyInterpreterState 。

到目前为止,仅有的两个对象建立起了联系。而对应到操作系统,就是进程和线程建立了联系。

而在两者建立了联系之后,那么就很容易在 PyInterpreterState 和 PyThreadState 之间穿梭。并且在 Python 运行时环境中,会有一个变量(先卖个关子)一直维护着当前活动的线程,更准确的说是当前活动线程(OS 线程)对应的 PyThreadState 对象。初始时,该变量为 NULL,在 Python 启动并创建了第一个 PyThreadState 之后,会调用 PyThreadState_Swap 函数来设置这个变量。

// Python/pystate.c
PyThreadState *
PyThreadState_Swap(PyThreadState *newts)
{
    // 调用了 _PyThreadState_Swap,里面传入了两个参数
    // 第一个我们后面说,从名字上看显然这是和 GIL 相关的
    // 第二个参数就是新创建的线程状态对象
    return _PyThreadState_Swap(&_PyRuntime.gilstate, newts);
}

PyThreadState *
_PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *newts)
{
    // 获取当前的线程状态对象,并且保证线程的安全
    PyThreadState *oldts = _PyRuntimeGILState_GetThreadState(gilstate);
    // 将 GIL 交给 newts,也就是新创建、即将获取执行权的线程状态对象
    _PyRuntimeGILState_SetThreadState(gilstate, newts);
    // ...
    return oldts;
}

所以逻辑很容易理解,有一个变量始终维护着当前活跃线程对应的线程状态对象,初始时它是个 NULL。而一旦解释器启动,并创建了第一个线程状态对象(显然对应主线程),那么就会将创建的线程状态对象交给这个变量保存。

如果调用 _PyThreadState_Swap 的时候,发现保存线程状态对象的变量不为 NULL,那么说明开启了多线程。变量保存的就是代码中的 oldts,也就是当前活动线程对应的线程状态对象,可由于它的时间片耗尽,解释器会剥夺它的执行权,然后交给 newts。那么 newts 就成为了新的当前活跃线程对应的线程状态对象,那么它也要交给变量进行保存。

而通过 _PyThreadState_Swap 可以看到,想要实现这一点,主要依赖两个宏。

// 通过 &(gilstate)->tstate_current 获取当前活动线程(的线程状态对象)
#define _PyRuntimeGILState_GetThreadState(gilstate) \
    ((PyThreadState*)_Py_atomic_load_relaxed(&(gilstate)->tstate_current))

// 将 newts 设置为新的活跃线程,可以理解为发生了线程切换
#define _PyRuntimeGILState_SetThreadState(gilstate, value) \
    _Py_atomic_store_relaxed(&(gilstate)->tstate_current, \
                             (uintptr_t)(value))

然后这两个宏里面出现了 _Py_atomic_load_relaxed、_Py_atomic_store_relaxed 和 &(gilstate)->tstate_current ,这些又是什么呢?还有到底是哪个变量在维护着当前活动线程对应的线程状态对象呢?其实那两个宏已经告诉你了。

//Include/internal/pycore_pystate.h
struct _gilstate_runtime_state {
    // GIL 检查是否启用
    int check_enabled;
    // 持有 GIL 的活动线程对应的线程状态对象
    _Py_atomic_address tstate_current;
    // 函数指针,用于获取栈桢
    PyThreadFrameGetter getframe;
    PyInterpreterState *autoInterpreterState;
    Py_tss_t autoTSSkey;
};

//Include/internal/pycore_atomic.h
#define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \
    _Py_atomic_store_explicit((ATOMIC_VAL), (NEW_VAL), _Py_memory_order_relaxed)
#define _Py_atomic_load_relaxed(ATOMIC_VAL) \
    _Py_atomic_load_explicit((ATOMIC_VAL), _Py_memory_order_relaxed)

#define _Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, ORDER) \
    atomic_store_explicit(&((ATOMIC_VAL)->_value), NEW_VAL, ORDER)
#define _Py_atomic_load_explicit(ATOMIC_VAL, ORDER) \
    atomic_load_explicit(&((ATOMIC_VAL)->_value), ORDER)

不难发现:

  • _Py_atomic_load_relaxed 调用了 _Py_atomic_load_explicit,_Py_atomic_load_explicit 又调用了 atomic_load_explicit。
  • _Py_atomic_store_relaxed 调用了 _Py_atomic_store_explicit,_Py_atomic_store_explicit 调用了 atomic_store_explicit。

而 atomic_load_explicit 和 atomic_store_explicit 是系统头文件 stdatomic.h 中定义的 api,这是在系统的 api 中修改的,所以说是线程安全的。

介绍完中间部分的内容,那么我们可以从头开始分析 Python 运行时环境的初始化了,我们说它是从 Py_NewInterpreter 开始的。

// Python/pylifecycle.c
PyThreadState *
Py_NewInterpreter(void)
{   
    // 线程状态对象
    PyThreadState *tstate = NULL;
    // 传入线程状态对象,调用 new_interpreter
    PyStatus status = new_interpreter(&tstate);
    if (_PyStatus_EXCEPTION(status)) {
        Py_ExitStatusException(status);
    }
    // 返回线程状态对象
    return tstate;
}

然后我们的重点是 new_interpreter 函数,进程状态对象的创建就是在这个函数里面发生的。

// Include/cpython/initconfig.h
// 程序执行的状态
typedef struct {
    enum {
        _PyStatus_TYPE_OK=0,     // 正常
        _PyStatus_TYPE_ERROR=1,  // 错误
        _PyStatus_TYPE_EXIT=2    // 退出
    } _type;
    const char *func;     // 发生错误的函数名
    const char *err_msg;  // 错误消息
    int exitcode;         // 退出码
} PyStatus;


// Python/pylifecycle.c
static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    PyStatus status;
    // 运行时初始化,如果出现异常直接返回
    status = _PyRuntime_Initialize();
    if (_PyStatus_EXCEPTION(status)) {
        return status;
    }
    // 获取运行时
    _PyRuntimeState *runtime = &_PyRuntime;
    if (!runtime->initialized) {
        return _PyStatus_ERR("Py_Initialize must be called first");
    }
    // GIL API 在多解释器环境下存在问题,无法正常工作,因此需要禁用 PyGILState_Check() 来避免问题
    _PyGILState_check_enabled = 0;
    // 创建进程状态对象
    PyInterpreterState *interp = PyInterpreterState_New();
    if (interp == NULL) {
        *tstate_p = NULL;
        return _PyStatus_OK();
    }
    // 根据进程状态对象创建线程状态对象,维护对应的 OS 线程的状态
    PyThreadState *tstate = PyThreadState_New(interp);
    if (tstate == NULL) {
        PyInterpreterState_Delete(interp);
        *tstate_p = NULL;
        return _PyStatus_OK();
    }
    // 将 GIL 的控制权交给创建的线程
    PyThreadState *save_tstate = PyThreadState_Swap(tstate);

    // 从当前或主进程状态对象复制配置到新的进程状态对象
    PyConfig *config;
    if (save_tstate != NULL) {
        config = &save_tstate->interp->config;
    } else {
        PyInterpreterState *main_interp = PyInterpreterState_Main();
        config = &main_interp->config;
    }
    status = _PyConfig_Copy(&interp->config, config);
    if (_PyStatus_EXCEPTION(status)) {
        return status;
    }
    config = &interp->config;
    
    // 异常系统初始化
    status = _PyExc_Init();
    if (_PyStatus_EXCEPTION(status)) {
        return status;
    }
    status = _PyErr_Init();
    if (_PyStatus_EXCEPTION(status)) {
        return status;
    }

    // 创建模块字典
    PyObject *modules = PyDict_New();
    if (modules == NULL) {
        return _PyStatus_ERR("can't make modules dictionary");
    }
    interp->modules = modules;
    
    // 初始化 sys 模块
    PyObject *sysmod = _PyImport_FindBuiltin("sys", modules);
    if (sysmod != NULL) {
        interp->sysdict = PyModule_GetDict(sysmod);
        if (interp->sysdict == NULL) {
            goto handle_error;
        }
        Py_INCREF(interp->sysdict);
        PyDict_SetItemString(interp->sysdict, "modules", modules);
        if (_PySys_InitMain(runtime, interp) < 0) {
            return _PyStatus_ERR("can't finish initializing sys");
        }
    }
    else if (PyErr_Occurred()) {
        goto handle_error;
    }
    
    // 初始化 builtins 模块
    PyObject *bimod = _PyImport_FindBuiltin("builtins", modules);
    if (bimod != NULL) {
        interp->builtins = PyModule_GetDict(bimod);
        if (interp->builtins == NULL)
            goto handle_error;
        Py_INCREF(interp->builtins);
    }
    else if (PyErr_Occurred()) {
        goto handle_error;
    }
    
    if (bimod != NULL && sysmod != NULL) {
        // 添加内置异常
        status = _PyBuiltins_AddExceptions(bimod);
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
        status = _PySys_SetPreliminaryStderr(interp->sysdict);
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
        
        status = _PyImportHooks_Init();
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
        
        // 初始化导入系统
        status = init_importlib(interp, sysmod);
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }

        status = init_importlib_external(interp);
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
        
        // 初始化编码
        status = _PyUnicode_InitEncodings(tstate);
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
        
        // 设置标准流
        status = init_sys_streams(interp);
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
        
        // 添加 main 模块
        status = add_main_module(interp);
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
        
        // 初始化 site 导入
        if (config->site_import) {
            status = init_import_size();
            if (_PyStatus_EXCEPTION(status)) {
                return status;
            }
        }
    }

    if (PyErr_Occurred()) {
        goto handle_error;
    }

    *tstate_p = tstate;
    return _PyStatus_OK();

handle_error:
    // 错误处理,如果失败则清理所有资源并恢复原状态
    PyErr_PrintEx(0);
    PyThreadState_Clear(tstate);
    PyThreadState_Swap(save_tstate);
    PyThreadState_Delete(tstate);
    PyInterpreterState_Delete(interp);

    *tstate_p = NULL;
    return _PyStatus_OK();
}

Python 在初始化运行时环境时,肯定也要对类型系统进行初始化等等,整体是一个非常庞大的过程。

到这里,我们对 new_interpreter 的探索算是有了一个阶段性的成功,我们创建了代表进程和线程概念的 PyInterpreterState 和 PyThreadState 对象,并在它们之间建立了联系。下面 new_interpreter 将进入另一个环节,设置系统 module。

创建 builtins 模块

在 new_interpreter 中创建了 PyInterpreterState 和 PyThreadState 对象之后,就会开始设置系统的 builtins 了。

static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    // ...
    // 申请一个 PyDictObject 对象,用于 sys.modules
    PyObject *modules = PyDict_New();
    if (modules == NULL) {
        return _PyStatus_ERR("can't make modules dictionary");
    }
    // 然后让 interp -> modules 维护 modules
    // 由于 interp 表示的是进程状态对象,这说明什么? 
    // 显然是该进程内的多个线程共享同一个 sys.modules
    interp->modules = modules;
    // 加载 sys 模块,所有的 module 对象都在 sys.modules 中
    PyObject *sysmod = _PyImport_FindBuiltin("sys", modules);
    if (sysmod != NULL) {
        interp->sysdict = PyModule_GetDict(sysmod);
        if (interp->sysdict == NULL) {
            goto handle_error;
        }
        Py_INCREF(interp->sysdict);
        PyDict_SetItemString(interp->sysdict, "modules", modules);
        if (_PySys_InitMain(runtime, interp) < 0) {
            return _PyStatus_ERR("can't finish initializing sys");
        }
    }
    else if (PyErr_Occurred()) {
        goto handle_error;
    }
    // 加载内置模块 builtins
    PyObject *bimod = _PyImport_FindBuiltin("builtins", modules);
    if (bimod != NULL) {
        // 设置内置名字空间
        interp->builtins = PyModule_GetDict(bimod);
        if (interp->builtins == NULL)
            goto handle_error;
        Py_INCREF(interp->builtins);
    }
    // ...
}  

整体还是比较清晰和直观的,另外我们说内置名字空间是由进程来维护的,因为进程就是用来为线程提供资源的。但是也能看出,一个进程内的多个线程共享同一个内置名字空间。显然这是非常合理的,不可能每开启一个线程,就为其创建一个 builtins。我们来从 Python 的角度证明这一点:

import threading
import builtins

def foo1():
    builtins.list, builtins.tuple = builtins.tuple, builtins.list

def foo2():
    print(f"猜猜下面代码会输出什么:")
    print("list:", list([1, 2, 3, 4, 5]))
    print("tuple:", tuple([1, 2, 3, 4, 5]))

f1 = threading.Thread(target=foo1)
f1.start()
f1.join()
threading.Thread(target=foo2).start()
"""
猜猜下面代码会输出什么:
list: (1, 2, 3, 4, 5)
tuple: [1, 2, 3, 4, 5]
"""

所有的内置对象和内置函数都在内置名字空间里面,可以通过 import builtins 获取、也可以直接通过 __builtins__ 这个变量来获取,当然这种方式拿到的是模块,再获取模块的 __dict__ 就是内置名字空间了。

我们在 foo1 中把 list 和 tuple 互换了,而这个结果显然也影响了 foo2 函数,这也说明了 builtins 模块是属于进程级别的,它被多个线程共享。当然不止内置名字空间,所有的 module 对象都是被多个线程共享的,所以是 interp->modules = modules

至于对 builtins 模块的初始化是在 _PyBuiltin_Init 函数中进行的。

// Python/bltinmodule.c

PyObject *
_PyBuiltin_Init(void)
{
    PyObject *mod, *dict, *debug;

    const PyConfig *config = &_PyInterpreterState_GET_UNSAFE()->config;

    if (PyType_Ready(&PyFilter_Type) < 0 ||
        PyType_Ready(&PyMap_Type) < 0 ||
        PyType_Ready(&PyZip_Type) < 0)
        return NULL;
    // 获取 builtins 模块
    mod = _PyModule_CreateInitialized(&builtinsmodule, PYTHON_API_VERSION);
    if (mod == NULL)
        return NULL;
    // 拿到模块内部的属性字典
    dict = PyModule_GetDict(mod);

    // ...
    // 将所有内置对象加入到 builtins 模块的属性字典中,下面这些东西应该不陌生吧
    SETBUILTIN("None",                  Py_None);
    SETBUILTIN("Ellipsis",              Py_Ellipsis);
    SETBUILTIN("NotImplemented",        Py_NotImplemented);
    SETBUILTIN("False",                 Py_False);
    SETBUILTIN("True",                  Py_True);
    SETBUILTIN("bool",                  &PyBool_Type);
    SETBUILTIN("memoryview",        &PyMemoryView_Type);
    SETBUILTIN("bytearray",             &PyByteArray_Type);
    SETBUILTIN("bytes",                 &PyBytes_Type);
    SETBUILTIN("classmethod",           &PyClassMethod_Type);
    SETBUILTIN("complex",               &PyComplex_Type);
    SETBUILTIN("dict",                  &PyDict_Type);
    SETBUILTIN("enumerate",             &PyEnum_Type);
    SETBUILTIN("filter",                &PyFilter_Type);
    SETBUILTIN("float",                 &PyFloat_Type);
    SETBUILTIN("frozenset",             &PyFrozenSet_Type);
    SETBUILTIN("property",              &PyProperty_Type);
    SETBUILTIN("int",                   &PyLong_Type);
    SETBUILTIN("list",                  &PyList_Type);
    SETBUILTIN("map",                   &PyMap_Type);
    SETBUILTIN("object",                &PyBaseObject_Type);
    SETBUILTIN("range",                 &PyRange_Type);
    SETBUILTIN("reversed",              &PyReversed_Type);
    SETBUILTIN("set",                   &PySet_Type);
    SETBUILTIN("slice",                 &PySlice_Type);
    SETBUILTIN("staticmethod",          &PyStaticMethod_Type);
    SETBUILTIN("str",                   &PyUnicode_Type);
    SETBUILTIN("super",                 &PySuper_Type);
    SETBUILTIN("tuple",                 &PyTuple_Type);
    SETBUILTIN("type",                  &PyType_Type);
    SETBUILTIN("zip",                   &PyZip_Type);
    debug = PyBool_FromLong(config->optimization_level == 0);
    if (PyDict_SetItemString(dict, "__debug__", debug) < 0) {
        Py_DECREF(debug);
        return NULL;
    }
    Py_DECREF(debug);

    return mod;
#undef ADD_TO_ALL
#undef SETBUILTIN
}

整个 _PyBuiltin__Init 函数的功能就是设置内置名字空间,不过设置的东西似乎少了很多,比如 dir、hasattr、setattr 等等,这些明显也是内置的,但是它们到哪里去了呢。其实,设置内置名字空间这个过程是分为两步的:

  • 通过 _PyModule_CreateInitialized 函数创建 PyModuleObject 对象,即 builtins 模块。
  • 获取 builtins 模块的属性字典(即内置名字空间),将 Python 的内置对象塞到里面。

我们上面只看到了第二步,其实在第一步创建 builtins 模块时就已经完成了大部分的属性设置工作。

// Include/moduleobject.h

// 包含了模块的核心信息
typedef struct PyModuleDef{
    PyModuleDef_Base m_base;
    // 模块名称
    const char* m_name;
    // 模块的文档字符串
    const char* m_doc;
    // 模块的额外内存大小,用于单进程多解释器
    // 如果不是多解释器模式,那么写 -1 即可
    Py_ssize_t m_size;
    // 模块内部定义的函数
    PyMethodDef *m_methods;
    // 用于定义模块导入时的特殊行为,比如多阶段初始化等高级特性
    struct PyModuleDef_Slot* m_slots;
    // 用于垃圾回收,负责遍历模块引用的对象
    traverseproc m_traverse;
    // 用于垃圾回收,负责清理模块状态
    inquiry m_clear;
    // 模块被销毁时调用的清理函数
    freefunc m_free;
} PyModuleDef;

// Object/moduleobject.c
PyObject *
_PyModule_CreateInitialized(struct PyModuleDef* module, int module_api_version)
{
    const char* name;
    PyModuleObject *m;
    // 初始化
    if (!PyModuleDef_Init(module))
        return NULL;
    // 拿到 module 的 name,对于当前来说就是 builtins
    name = module->m_name;
    // 这里比较有意思,这是检测模块版本的,针对的是需要被导入的 py 文件
    // 解释器在导入 py 文件时,会优先从当前目录的 __pycache__ 里面加载 pyc
    // 而 pyc 文件包含了编译时解释器的 MAGIC NUMBER,所以要比较是否一致
    // 如果不一致,说明解释器版本不对,则不导入 pyc 文件,而是会重新编译 py 文件    
    if (!check_api_version(name, module_api_version)) {
        return NULL;
    }
    if (module->m_slots) {
        PyErr_Format(
            PyExc_SystemError,
            "module %s: PyModule_Create is incompatible with m_slots", name);
        return NULL;
    }
    // ...
    // 创建一个 PyModuleObject 实例
    if ((m = (PyModuleObject*)PyModule_New(name)) == NULL)
        return NULL;
    // 如果 m_size > 0,说明当前是多解释器模式,这个不需要关注
    // 因为多解释器模式只能手动调用 Python/C API 开启(用得也很少)
    // 像我们平时在使用 Python 时,都是一个进程对应一个解释器
    if (module->m_size > 0) {
        m->md_state = PyMem_MALLOC(module->m_size);
        if (!m->md_state) {
            PyErr_NoMemory();
            Py_DECREF(m);
            return NULL;
        }
        memset(m->md_state, 0, module->m_size);
    }
    // 这里的变量 module 指向 PyModuleDef,它包含了模块的核心信息
    // 而变量 m 指向的 PyModuleObject 才是模块对象,它是基于 PyModuleDef 构建的
    if (module->m_methods != NULL) {
        // module->m_methods 指向 builtin_methods 数组,该数组中包含了大量的内置函数
        // 调用 PyModule_AddFunctions 将它们添加到模块中
        if (PyModule_AddFunctions((PyObject *) m, module->m_methods) != 0) {
            Py_DECREF(m);
            return NULL;
        }
    }
    if (module->m_doc != NULL) {
        // 设置 docstring
        if (PyModule_SetDocString((PyObject *) m, module->m_doc) != 0) {
            Py_DECREF(m);
            return NULL;
        }
    }
    m->md_def = module;
    return (PyObject*)m;
}

所以在创建模块之后,就已经将大量的内置函数添加到 builtins 模块里面了。然后来看一下模块的具体创建过程。

创建 module 对象

Python 的 module 对象在底层对应 PyModuleObject 结构体,来看看它长什么样子。

// Objects/moduleobject.c
typedef struct {
    // 头部信息
    PyObject_HEAD
    // 属性字典,所有的属性和值都在里面
    PyObject *md_dict;
    // module 对象的核心信息
    struct PyModuleDef *md_def;
    void *md_state;
    PyObject *md_weaklist;
    PyObject *md_name;
} PyModuleObject;

而这个对象是通过 PyModule_New 创建的。

// Objects/moduleobject.c
PyObject *
PyModule_New(const char *name)
{
    PyObject *nameobj, *module;
    // 模块的名称
    nameobj = PyUnicode_FromString(name);
    if (nameobj == NULL)
        return NULL;
    // 创建 PyModuleObject
    module = PyModule_NewObject(nameobj);
    Py_DECREF(nameobj);
    return module;
}

PyObject *
PyModule_NewObject(PyObject *name)
{
    PyModuleObject *m;
    // 为模块对象申请内存空间
    m = PyObject_GC_New(PyModuleObject, &PyModule_Type);
    if (m == NULL)
        return NULL;
    // 初始化内部字段
    m->md_def = NULL;
    m->md_state = NULL;
    m->md_weaklist = NULL;
    m->md_name = NULL;
    // 属性字典
    m->md_dict = PyDict_New();
    // 调用 module_init_dict 初始化属性字典
    if (module_init_dict(m, m->md_dict, name, NULL) != 0)
        goto fail;
    PyObject_GC_Track(m);
    return (PyObject *)m;

 fail:
    Py_DECREF(m);
    return NULL;
}

static int
module_init_dict(PyModuleObject *mod, PyObject *md_dict,
                 PyObject *name, PyObject *doc)
{
    _Py_IDENTIFIER(__name__);
    _Py_IDENTIFIER(__doc__);
    _Py_IDENTIFIER(__package__);
    _Py_IDENTIFIER(__loader__);
    _Py_IDENTIFIER(__spec__);

    if (md_dict == NULL)
        return -1;
    if (doc == NULL)
        doc = Py_None;
    // 模块的一些属性,如 __name__、__doc__等等
    if (_PyDict_SetItemId(md_dict, &PyId___name__, name) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___doc__, doc) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___package__, Py_None) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___loader__, Py_None) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___spec__, Py_None) != 0)
        return -1;
    if (PyUnicode_CheckExact(name)) {
        Py_INCREF(name);
        Py_XSETREF(mod->md_name, name);
    }

    return 0;
}

这里虽然创建了一个 module 对象,但这仅仅是一个空的 module 对象,并没有包含相应的操作和数据。我们看到只设置了 name 和 doc 等属性。

设置 module 对象的属性

在 _PyModule_CreateInitialized 函数中调用 PyModule_New 创建了一个模块,然后通过 PyModule_AddFunctions 完成了对 builtins 大部分属性的设置。这个设置的属性依赖于第二个参数 module->m_methods,在这里为 builtin_methods。

// Python/bltinmodule.c
static PyMethodDef builtin_methods[] = {
    {"__build_class__", (PyCFunction)(void(*)(void))builtin___build_class__,
     METH_FASTCALL | METH_KEYWORDS, build_class_doc},
    {"__import__",      (PyCFunction)(void(*)(void))builtin___import__, METH_VARARGS | METH_KEYWORDS, import_doc},
    BUILTIN_ABS_METHODDEF
    BUILTIN_ALL_METHODDEF
    BUILTIN_ANY_METHODDEF
    BUILTIN_ASCII_METHODDEF
    BUILTIN_BIN_METHODDEF
    {"breakpoint",      (PyCFunction)(void(*)(void))builtin_breakpoint, METH_FASTCALL | METH_KEYWORDS, breakpoint_doc},
    BUILTIN_CALLABLE_METHODDEF
    BUILTIN_CHR_METHODDEF
    BUILTIN_COMPILE_METHODDEF
    BUILTIN_DELATTR_METHODDEF
    {"dir",             builtin_dir,        METH_VARARGS, dir_doc},
    BUILTIN_DIVMOD_METHODDEF
    BUILTIN_EVAL_METHODDEF
    BUILTIN_EXEC_METHODDEF
    BUILTIN_FORMAT_METHODDEF
    {"getattr",         (PyCFunction)(void(*)(void))builtin_getattr, METH_FASTCALL, getattr_doc},
    BUILTIN_GLOBALS_METHODDEF
    BUILTIN_HASATTR_METHODDEF
    BUILTIN_HASH_METHODDEF
    BUILTIN_HEX_METHODDEF
    BUILTIN_ID_METHODDEF
    BUILTIN_INPUT_METHODDEF
    BUILTIN_ISINSTANCE_METHODDEF
    BUILTIN_ISSUBCLASS_METHODDEF
    {"iter",            (PyCFunction)(void(*)(void))builtin_iter,       METH_FASTCALL, iter_doc},
    BUILTIN_LEN_METHODDEF
    BUILTIN_LOCALS_METHODDEF
    {"max",             (PyCFunction)(void(*)(void))builtin_max,        METH_VARARGS | METH_KEYWORDS, max_doc},
    {"min",             (PyCFunction)(void(*)(void))builtin_min,        METH_VARARGS | METH_KEYWORDS, min_doc},
    {"next",            (PyCFunction)(void(*)(void))builtin_next,       METH_FASTCALL, next_doc},
    BUILTIN_OCT_METHODDEF
    BUILTIN_ORD_METHODDEF
    BUILTIN_POW_METHODDEF
    {"print",           (PyCFunction)(void(*)(void))builtin_print,      METH_FASTCALL | METH_KEYWORDS, print_doc},
    BUILTIN_REPR_METHODDEF
    BUILTIN_ROUND_METHODDEF
    BUILTIN_SETATTR_METHODDEF
    BUILTIN_SORTED_METHODDEF
    BUILTIN_SUM_METHODDEF
    {"vars",            builtin_vars,       METH_VARARGS, vars_doc},
    {NULL,              NULL},
};

怎么样,是不是看到了玄机。

总结一下就是:在 Py_NewInterpreter 里面调用 new_interpreter 函数,然后在 new_interpreter 函数里面,通过 PyInterpreterState_New 创建 PyInterpreterState 对象,然后以 PyInterpreterState 对象为参数,调用 PyThreadState_New 函数创建 PyThreadState 对象。

接着就是执行各种初始化动作,然后调用 _PyBuiltin_Init 设置内置属性。然而在 _PyBuiltin_Init 函数的最后设置的都是一些内置的类对象,而内置函数(比如 getattr、exec 等)却没有设置。

所以 _PyBuiltin_Init 函数中间调用的 _PyModule_CreateInitialized 不仅仅是初始化一个 module 对象,还会在初始化之后将我们没有看到的一些属性设置进去。在里面先使用 PyModule_New 创建一个 PyModuleObject,此时它内部只有 __name__ 和 __doc__ 等属性,之后再通过 PyModule_AddFunctions 设置 builtin_methods,在 builtin_methods 里面我们看到了 dir、getattr 等内置函数。当这些属性设置完之后,退回到 _PyBuiltin_Init 函数中,再设置剩余的部分属性(内置的类对象和一些常量)。之后,builtins 模块就完成了。

另外 builtin_methods 是一个 PyMethodDef 类型的数组,里面是一个个的 PyMethodDef 结构体实例。

// Include/methodobject.h

typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
typedef PyObject *(*_PyCFunctionFast) (PyObject *, PyObject *const *, Py_ssize_t);
typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *, PyObject *);

struct PyMethodDef {
    // 内置函数的名称
    const char  *ml_name;
    // 具体实现对应的 C 函数
    PyCFunction ml_meth;
    // 函数类型
    /* #define METH_VARARGS  0x0001  支持扩展位置参数
     * #define METH_KEYWORDS 0x0002  支持扩展关键字参数
     * #define METH_NOARGS   0x0004  不需要参数
     * #define METH_O        0x0008  精确接收一个位置参数
     * #define METH_CLASS    0x0010  被 classmethod 装饰的类方法
     * #define METH_STATIC   0x0020  被 staticmethod 装饰的静态方法
     */
    int         ml_flags;
    // 函数的 __doc__
    const char  *ml_doc;
};
typedef struct PyMethodDef PyMethodDef;

对于这里面每一个 PyMethodDef 实例,_PyModule_CreateInitialized 都会基于它创建一个 PyCFunctionObject 对象,我们说过内置函数以及内置实例对象的方法在底层会对应 PyCFunctionObject。

// Include/methodobject.h

typedef struct {
    // 头部信息
    PyObject_HEAD
    // PyMethodDef 实例
    PyMethodDef *m_ml;
    // self 
    PyObject    *m_self;
    // __module__
    PyObject    *m_module;
    // 弱引用列表,不讨论
    PyObject    *m_weakreflist;
    // 为了效率而单独实现的矢量调用函数
    vectorcallfunc vectorcall;
} PyCFunctionObject;

然后 PyCFunctionObject 的创建则是通过 PyCFunction_New 完成的。

// Objects/methodobject.c
PyObject *
PyCFunction_New(PyMethodDef *ml, PyObject *self)
{
    return PyCFunction_NewEx(ml, self, NULL);
}

PyObject *
PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
{
    vectorcallfunc vectorcall;
    // 判断参数类型,决定调用方式
    switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
    {
        case METH_VARARGS:
        case METH_VARARGS | METH_KEYWORDS:
            /* For METH_VARARGS functions, it's more efficient to use tp_call
             * instead of vectorcall. */
            vectorcall = NULL;
            break;
        case METH_FASTCALL:
            vectorcall = cfunction_vectorcall_FASTCALL;
            break;
        case METH_FASTCALL | METH_KEYWORDS:
            vectorcall = cfunction_vectorcall_FASTCALL_KEYWORDS;
            break;
        case METH_NOARGS:
            vectorcall = cfunction_vectorcall_NOARGS;
            break;
        case METH_O:
            vectorcall = cfunction_vectorcall_O;
            break;
        default:
            PyErr_Format(PyExc_SystemError,
                         "%s() method: bad call flags", ml->ml_name);
            return NULL;
    }

    PyCFunctionObject *op;
    // 我们看到 PyCFunctionObject 也采用了缓存池
    op = free_list;
    if (op != NULL) {
        free_list = (PyCFunctionObject *)(op->m_self);
        (void)PyObject_INIT(op, &PyCFunction_Type);
        numfree--;
    }
    else {
        op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);
        if (op == NULL)
            return NULL;
    }
    // 设置属性
    op->m_weakreflist = NULL;
    op->m_ml = ml;
    Py_XINCREF(self);
    op->m_self = self;
    Py_XINCREF(module);
    op->m_module = module;
    op->vectorcall = vectorcall;
    _PyObject_GC_TRACK(op);
    return (PyObject *)op;
}

以上就是 _PyBuiltin__Init 所做的事情,再之后虚拟机会把 PyModuleObject 对象的属性字典抽取出来,赋值给 interp -> builtins

// Python/pylifecycle.c
static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    // ...
    PyObject *bimod = _PyImport_FindBuiltin("builtins", modules);
    if (bimod != NULL) {
        // 通过 PyModule_GetDict 获取属性字典,赋值给 builtins
        interp->builtins = PyModule_GetDict(bimod);
        if (interp->builtins == NULL)
            goto handle_error;
        Py_INCREF(interp->builtins);
    }
    else if (PyErr_Occurred()) {
        goto handle_error;
    }
    // ...
}

以后 Python 在访问内置名字空间时,直接访问 interp->builtins 就可以了,因为内置属性的使用会很频繁,所以这种加速机制是很有效的。

创建 sys 模块

Python 在创建 builtins 模块、设置内置名字空间之前,会先创建 sys 模块,流程是一样的,只是我们将介绍的顺序颠倒了一下。

static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    // ...
    // 创建 sys 模块
    PyObject *sysmod = _PyImport_FindBuiltin("sys", modules);
    if (sysmod != NULL) {
        // 获取 sys 模块的属性字典,并赋值给 interp->sysdict
        interp->sysdict = PyModule_GetDict(sysmod);
        if (interp->sysdict == NULL) {
            goto handle_error;
        }
        Py_INCREF(interp->sysdict);
        // 将 "modules": modules 添加到 sys 模块的属性字典中
        // 在 Python 里面便可通过 sys.modules 拿到所有的模块
        PyDict_SetItemString(interp->sysdict, "modules", modules);
        if (_PySys_InitMain(runtime, interp) < 0) {
            return _PyStatus_ERR("can't finish initializing sys");
        }
    }
    else if (PyErr_Occurred()) {
        goto handle_error;
    }
    // ...
}

创建 sys module 之后,还会额外添加一个 __main__ 模块。

static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    // ...
    status = add_main_module(interp);
    if (_PyStatus_EXCEPTION(status)) {
        return status;
    }  
    // ...
}  

static PyStatus
add_main_module(PyInterpreterState *interp)
{
    PyObject *m, *d, *loader, *ann_dict;
    // 将 __main__ 添加进 sys.modules 中
    m = PyImport_AddModule("__main__");
    if (m == NULL)
        return _PyStatus_ERR("can't create __main__ module");

    d = PyModule_GetDict(m);
    ann_dict = PyDict_New();
    if ((ann_dict == NULL) ||
        (PyDict_SetItemString(d, "__annotations__", ann_dict) < 0)) {
        return _PyStatus_ERR("Failed to initialize __main__.__annotations__");
    }
    Py_DECREF(ann_dict);

    if (PyDict_GetItemString(d, "__builtins__") == NULL) {
        PyObject *bimod = PyImport_ImportModule("builtins");
        if (bimod == NULL) {
            return _PyStatus_ERR("Failed to retrieve builtins module");
        }
        if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
            return _PyStatus_ERR("Failed to initialize __main__.__builtins__");
        }
        Py_DECREF(bimod);
    }
  
    loader = PyDict_GetItemString(d, "__loader__");
    if (loader == NULL || loader == Py_None) {
        PyObject *loader = PyObject_GetAttrString(interp->importlib,
                                                  "BuiltinImporter");
        if (loader == NULL) {
            return _PyStatus_ERR("Failed to retrieve BuiltinImporter");
        }
        if (PyDict_SetItemString(d, "__loader__", loader) < 0) {
            return _PyStatus_ERR("Failed to initialize __main__.__loader__");
        }
        Py_DECREF(loader);
    }
    return _PyStatus_OK();
}

这个 __main__ 估计不用我多说了。之前在 PyModule_New 中,创建一个 PyModuleObject 对象之后,会在其属性字典中插入一个名为 "__name__" 的 key,value 就是 "__main__"。但是对于当前模块来说,这个模块也叫做 __main__。

name = "古明地觉"

import __main__
print(__main__.name)  # 古明地觉

import sys
print(sys.modules["__main__"] is __main__)  # True

因此我们算是知道了,为什么执行 python xxx.py 的时候,__name__ 是 "__main__" 了,因为这里设置了。而 Python 沿着名字空间寻找的时候,最终会在 __main__ 的 local 空间中发现 __name__,且值为字符串 "__main__"。但如果是以 import 的方式加载的,那么 __name__ 则不是 "__main__",而是模块名。

其实这个 __main__ 我们再熟悉不过了,当输入 dir() 的时候,就会显示 __main__ 的内容。dir 可以不加参数,如果不加参数,那么默认访问当前的 local 空间,也就是 __main__。

>>> __name__
'__main__'
>>> 
>>> __builtins__.__name__
'builtins'
>>>
>>> import numpy as np
>>> np.__name__
'numpy'

所以说,访问模块就类似访问变量一样,modules 里面存放了所有的 <模块名, 模块对象>。当我们访问 np 的时候,会找到 name 为 "numpy" 的模块,然后这个值里面也维护了一个字典,其中也有一个 key 为 "__name__" 的 entry,value 为 "numpy"。

设置 site-specific 的 module 搜索路径

Python 是一个非常开放的体系,它的强大来源于丰富的第三方库,这些库由外部的 py 文件来提供。当使用这些第三方库的时候,只需要简单地进行 import 即可。一般来说,这些第三方库都放在 Lib/site-packages 中,如果程序想使用这些库,直接导入即可。

但是到目前为止,我们好像也没看到 Python 将 site-packages 路径设置到搜索路径里面去啊。其实在完成了 __main__ 的创建之后,Python 才腾出手来收拾这个 site-package。这个关键的动作在于 Python 的一个标准库:site.py。

我们先将 Lib 目录下的 site.py 删掉,然后导入一个第三方模块,看看会有什么后果。

因此 Python 在初始化的过程中确实导入了 site.py,所以才有了如下的输出。而这个 site.py 也正是 Python 能正确加载位于 site-packages 目录下第三方包的关键所在。我们可以猜测,应该就是这个 site.py 将 site-packages 目录加入到了 sys.path 中,而这个动作是由 init_import_size 完成的。

static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    // ...
    if (config->site_import) {
        status = init_import_size();
        if (_PyStatus_EXCEPTION(status)) {
            return status;
        }
    }
    // ...
}

static PyStatus
init_import_size(void)
{
    PyObject *m;
    // 导入 site 模块,在 site 模块里面会将 site-packages 加入到 sys.path 中
    m = PyImport_ImportModule("site");
    // 如果导入失败,抛出异常
    if (m == NULL) {
        // 这里的报错信息是不是和上图中显示的一样呢?
        return _PyStatus_ERR("Failed to import the site module");
    }
    Py_DECREF(m);
    return _PyStatus_OK();
}

在 init_import_size 中,只调用了 PyImport_ImportModule 函数,这个函数是 import 机制的核心所在。比如 PyImport_ImportModule("numpy") 就等价于 import numpy

小结

以上就是运行时环境的初始化所做的事情,但需要注意:此时虚拟机还没有完全启动。对,上面的那些工作都只是前戏,是虚拟机启动之前所做的一些准备工作。而在准备工作做完之后,虚拟机就会正式启动。那么怎么启动呢?我们下一篇文章再聊。


 

欢迎大家关注我的公众号:古明地觉的编程教室。

如果觉得文章对你有所帮助,也可以请作者吃个馒头,Thanks♪(・ω・)ノ。