Python Import机制(一)

理解Python Import机制可以对Python的Package结构进行更深刻的理解。

1.__import__函数

__import__函数是import关键字的内部实现。

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

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOOi:__import__",
                kwlist, &name, &globals, &locals, &fromlist, &level))
        return NULL;

    return PyImport_ImportModuleLevel(name, globals, locals,
                                  fromlist, level);
}

builtin___import__函数内部很简单,主要是提取kwlist列出的几个参数,然后就进入了PyImport_ImportModuleLevel方法内。PyImport_ImportModuleLevel函数只是对即将进行的操作进行加锁,然后调用方法import_module_level

static PyObject *
import_module_level(char *name, PyObject *globals, PyObject *locals,
                    PyObject *fromlist, int level) {

    // [1] 获取import发生时的package
    parent = get_parent(globals, buf, &buflen, level);

    // [2] 解析module的路径结构,依次加载每一个package/module
    head = load_next(parent, level < 0 ? Py_None : parent, &name, buf,
                    &buflen);
    if (head == NULL)
        goto error_exit;

    tail = head;
    Py_INCREF(tail);
    while (name) {
        next = load_next(tail, tail, &name, buf, &buflen);
        Py_DECREF(tail);
        if (next == NULL) {
            Py_DECREF(head);
            goto error_exit;
        }
        tail = next;
    }

    // import xx.xx.xx 形式
    if (fromlist == NULL) {
        Py_DECREF(tail);
        PyMem_FREE(buf);
        return head;
    }

    Py_DECREF(head);
    // from xx.xx import xx 形式
    if (!ensure_fromlist(tail, fromlist, buf, buflen, 0)) {
        Py_DECREF(tail);
        goto error_exit;
    }

    PyMem_FREE(buf);
    return tail;
}

get_parent方法用于获取当前import动作所在的package。load_next可以认为是在导入name中声明的每一个模块,每次调用load_next都会返回本次导入的模块的PyObject,并且修改name的信息,将上次导入的模块的名称删除。

2.get_parent函数

static PyObject *
get_parent(PyObject *globals, char *buf, Py_ssize_t *p_buflen, int level)
{

    ......
    // 获取__package__信息,就是当前import的环境下,其所在的package叫什么名字
    pkgname = PyDict_GetItem(globals, pkgstr);

    if ((pkgname != NULL) && (pkgname != Py_None)) {
        ......
        // 如果package是存在的,那么我们直接使用它
        // 将package的内容直接设置到buf里面
        strcpy(buf, PyString_AS_STRING(pkgname));
    } else {
        // __package__并没有设置,我们通过其他方法找出来
        // 先获得我们import的环境的__name__变量
        modname = PyDict_GetItem(globals, namestr);
        if (modname == NULL || !PyString_Check(modname))
            return Py_None;

        // 再获得__path__变量,需要知道的一点是在__init__.py
        // 内部__path__变量是存在的,但其他module内部__path__
        // 变量是为None的
        modpath = PyDict_GetItem(globals, pathstr);
        if (modpath != NULL) {
            ......
            // __path__已经被设置了,那么__name__的值就是package的名称
            // 将package的内容直接设置到buf里面
            strcpy(buf, PyString_AS_STRING(modname));
            ......
        } else {
            // __package__名称为空,那么import的动作是在某个module内部的
            char *start = PyString_AS_STRING(modname);
            // lastdot就是当前module所在的package的名称
            char *lastdot = strrchr(start, '.');
            size_t len;
            int error;
            if (lastdot == NULL && level > 0) {
                PyErr_SetString(PyExc_ValueError,
                    "Attempted relative import in non-package");
                return NULL;
            }
            if (lastdot == NULL) {
                // 我们import的环境是最上层的环境
                // 比如说得命令行环境
                // 程序最初的运行文件等
                error = PyDict_SetItem(globals, pkgstr, Py_None);
                if (error) {
                    PyErr_SetString(PyExc_ValueError,
                        "Could not set __package__");
                    return NULL;
                }
                return Py_None;
            }
            len = lastdot - start;
            // 将__package__的信息设置到buf里面去
            strncpy(buf, start, len);
            ......
        }
    }
    // level > 0 的情况很多时候都是
    // from ... import a.b
    // 上面的level=3
    while (--level > 0) {
        char *dot = strrchr(buf, '.');
        if (dot == NULL) {
            PyErr_SetString(PyExc_ValueError,
                "Attempted relative import beyond "
                "toplevel package");
            return NULL;
        }
        *dot = '\0';
    }
    *p_buflen = strlen(buf);

    modules = PyImport_GetModuleDict();
    // 从sys.modules中获取对应的package
    parent = PyDict_GetItemString(modules, buf);
    return parent;
}    

3.load_next函数

static PyObject *
load_next(PyObject *mod, PyObject *altmod, char **p_name, char *buf,
          Py_ssize_t *p_buflen)
{
    ... ...
    // p_name指向被截取后的路径名称
    if (dot == NULL) {
        *p_name = NULL;
        len = strlen(name);
    }
    else {
        *p_name = dot+1;
        len = dot-name;
    }
    ... ...
    // 修改buf的内容,将截取的模块内容拷贝到buf后
    p = buf + *p_buflen;
    if (p != buf)
        *p++ = '.';
    if (p+len-buf >= MAXPATHLEN) {
        PyErr_SetString(PyExc_ValueError,
                        "Module name too long");
        return NULL;
    }
    strncpy(p, name, len);
    p[len] = '\0';
    *p_buflen = p+len-buf;

    // import模块
    result = import_submodule(mod, p, buf);
    if (result == Py_None && altmod != mod) {
        //如果没有import成功,那么我们从默认路径开始import
        Py_DECREF(result);
        /* Here, altmod must be None and mod must not be None */
        result = import_submodule(altmod, p, p);
        if (result != NULL && result != Py_None) {
            if (mark_miss(buf) != 0) {
                Py_DECREF(result);
                return NULL;
            }
            strncpy(buf, name, len);
            buf[len] = '\0';
            *p_buflen = len;
        }
    }
    ... ...

    return result;
}

4.领悟

  1. import是不能import进module内部的类的,比如说module A中实现了类A,那么import A_p.A.A会有问题,需要改为from A_p.A import A
  2. 对应from A_p.A import a形式,我们可以看成__import__(name = “A_p.A”, from_list = [“a”]),函数返回的是module A