大师兄的Python源码学习笔记(三十六): 模块的动态加载机制(三)

大师兄的Python源码学习笔记(三十五): 模块的动态加载机制(二)
大师兄的Python源码学习笔记(三十七): 模块的动态加载机制(四)

三、import机制的实现

  • 通过黑盒探索,可以发现Python的import机制可分为三个不同的功能:
  1. Python运行时的全局module pool的维护和搜索;
  2. 解析与搜索module路径的树状结构;
  3. 对不同文件格式的module的动态加载机制。
  • 虽然Python中import的表现千变万化,但终归可以总结为 import x.y.z的默认形式。
  • 从前面的章节可以看到,import机制的起点是builtin module中的__import__操作,也就是builtin__import___函数:
Python\bltinmodule.c

static PyObject *
builtin___import__(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"name", "globals", "locals", "fromlist",
                             "level", 0};
    PyObject *name, *globals = NULL, *locals = NULL, *fromlist = NULL;
    int level = 0;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|OOOi:__import__",
                    kwlist, &name, &globals, &locals, &fromlist, &level))
        return NULL;
    return PyImport_ImportModuleLevelObject(name, globals, locals,
                                            fromlist, level);
}
  • 可以看出在这里的PyArg_ParseTupleAndKeywords函数是核心,它的功能是将argskwds中所包含的所有对象按format中指定的格式解析成各种目标对象:
Python\getargs.c

/* Support for keyword arguments donated by
   Geoff Philbrick <philbric@delphi.hks.com> */

/* Return false (0) for error, else true. */
int
PyArg_ParseTupleAndKeywords(PyObject *args,
                            PyObject *keywords,
                            const char *format,
                            char **kwlist, ...)
{
    int retval;
    va_list va;

    if ((args == NULL || !PyTuple_Check(args)) ||
        (keywords != NULL && !PyDict_Check(keywords)) ||
        format == NULL ||
        kwlist == NULL)
    {
        PyErr_BadInternalCall();
        return 0;
    }

    va_start(va, kwlist);
    retval = vgetargskeywords(args, keywords, format, kwlist, &va, 0);
    va_end(va);
    return retval;
}
  • 所谓目标对象可以是Python中的对象,也可以是C的原生类型。
  • 这里的args参数实际上是一个PyTupleObject对象,包含了builtin__import__函数运行所需要的所有参数和信息,它是虚拟机在执行IMPORT_NAME指令时打包产生的,这里虚拟机进行了一个逆动作,即将PyTupleObject对象拆开,重新获得当初的参数。
  • 指令解析格式的format参数中可用的格式字符非常多,这里简单介绍builtin___import__用到的字符:
{"name", "globals", "locals", "fromlist","level", 0}
U|OOOi:__import__

U:代表目标对象是一个char*,用来将tuple中的PyUnicodeObject对象解析成char*。
i:用来将tuple中的PyIntObject对象解析为int类型的值。
O:代表解析的目标对象依然是一个Python中的核发对象,通常表示不进行任何解析和转换。
|:非格式字符,表示其后所带的格式字符是可选的。
::指示格式字符到此结束,其后所带的字符串用于在解析过程中出错时输出错误信息时使用。

  • 在完成拆包动作后,Python进入了PyImport_ImportModuleLevelObject:
Python\import.c

PyObject *
PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
                                 PyObject *locals, PyObject *fromlist,
                                 int level)
{
    _Py_IDENTIFIER(_handle_fromlist);
    PyObject *abs_name = NULL;
    PyObject *final_mod = NULL;
    PyObject *mod = NULL;
    PyObject *package = NULL;
    PyInterpreterState *interp = PyThreadState_GET()->interp;
    int has_from;
    ... ...
    mod = PyImport_GetModule(abs_name);
    if (mod != NULL && mod != Py_None) {
        _Py_IDENTIFIER(__spec__);
        _Py_IDENTIFIER(_initializing);
        _Py_IDENTIFIER(_lock_unlock_module);
        PyObject *value = NULL;
        PyObject *spec;
        int initializing = 0;

        /* Optimization: only call _bootstrap._lock_unlock_module() if
           __spec__._initializing is true.
           NOTE: because of this, initializing must be set *before*
           stuffing the new module in sys.modules.
         */
        spec = _PyObject_GetAttrId(mod, &PyId___spec__);
        if (spec != NULL) {
            value = _PyObject_GetAttrId(spec, &PyId__initializing);
            Py_DECREF(spec);
        }
        if (value == NULL)
            PyErr_Clear();
        else {
            initializing = PyObject_IsTrue(value);
            Py_DECREF(value);
            if (initializing == -1)
                PyErr_Clear();
            if (initializing > 0) {
                value = _PyObject_CallMethodIdObjArgs(interp->importlib,
                                                &PyId__lock_unlock_module, abs_name,
                                                NULL);
                if (value == NULL)
                    goto error;
                Py_DECREF(value);
            }
        }
    }
    else {
        Py_XDECREF(mod);
        mod = import_find_and_load(abs_name);
        if (mod == NULL) {
            goto error;
        }
    }

    has_from = 0;
    if (fromlist != NULL && fromlist != Py_None) {
        has_from = PyObject_IsTrue(fromlist);
        if (has_from < 0)
            goto error;
    }
    if (!has_from) {
        Py_ssize_t len = PyUnicode_GET_LENGTH(name);
        if (level == 0 || len > 0) {
            Py_ssize_t dot;

            dot = PyUnicode_FindChar(name, '.', 0, len, 1);
            if (dot == -2) {
                goto error;
            }

            if (dot == -1) {
                /* No dot in module name, simple exit */
                final_mod = mod;
                Py_INCREF(mod);
                goto error;
            }

            if (level == 0) {
                PyObject *front = PyUnicode_Substring(name, 0, dot);
                if (front == NULL) {
                    goto error;
                }

                final_mod = PyImport_ImportModuleLevelObject(front, NULL, NULL, NULL, 0);
                Py_DECREF(front);
            }
            else {
                Py_ssize_t cut_off = len - dot;
                Py_ssize_t abs_name_len = PyUnicode_GET_LENGTH(abs_name);
                PyObject *to_return = PyUnicode_Substring(abs_name, 0,
                                                        abs_name_len - cut_off);
                if (to_return == NULL) {
                    goto error;
                }

                final_mod = PyImport_GetModule(to_return);
                Py_DECREF(to_return);
                if (final_mod == NULL) {
                    PyErr_Format(PyExc_KeyError,
                                 "%R not in sys.modules as expected",
                                 to_return);
                    goto error;
                }
            }
        }
        else {
            final_mod = mod;
            Py_INCREF(mod);
        }
    }
    else {
        final_mod = _PyObject_CallMethodIdObjArgs(interp->importlib,
                                                  &PyId__handle_fromlist, mod,
                                                  fromlist, interp->import_func,
                                                  NULL);
    }
... ...
  • 在上面的代码中,虚拟机会对import动作上锁和解锁,目的是为了同步不同的线程对同一个moduleimport动作。
  • 代码中的fromlist通常是Py_None,但当Python虚拟机进行类似from x impory y,z这样的动作时,fromlist就成为一个诸如(y,z)这样的PyTupleObject对象。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。