Python Import机制(二)

这一篇接着上一个篇继续讨论Python Import的源码。

1.关于import_submodule

import_submodule函数做的事情就是将指定的module(或者package)加载进来。

static PyObject *
import_submodule(PyObject *mod, char *subname, char *fullname)
{
    // [1] 输入参数的简单解释
    // mod : 两种情况,一种是package,一种是None
    // subname : 将要import的module名称
    // fullname : import的全路径

    if ((m = PyDict_GetItemString(modules, fullname)) != NULL) {
        // [2] 查看在不在sys.modules里面
        Py_INCREF(m);
    }
    else {
        if (mod == Py_None)
            // path为NULL意味着import的搜索路径从sys.path的默认路径开始
            path = NULL;
        else {
            // 这里的mod一定是package
            path = PyObject_GetAttrString(mod, "__path__");
        }
        // [3] 找出对应的module,同时会返回文件描述符(fp),fdp会描述相关信息
        fdp = find_module(fullname, subname, path, buf, MAXPATHLEN+1,
                      &fp, &loader);
        // [4] import 对应的module
        m = load_module(fullname, fp, buf, fdp->type, loader);

    }
    return m;
}

2.find_module函数

static struct filedescr *
find_module(char *fullname, char *subname, PyObject *path, char *buf,
            size_t buflen, FILE **p_fp, PyObject **p_loader)
{

    /* sys.meta_path import hook */
    if (p_loader != NULL) {
        PyObject *meta_path;

        meta_path = PySys_GetObject("meta_path");  // sys.meta_path
        /* meta_path里面是用户hook的一些类,默认是空
           这些类实现了find_module方法,并返回对应的
           loader
         */
        npath = PyList_Size(meta_path);
        for (i = 0; i < npath; i++) {
            PyObject *loader;
            PyObject *hook = PyList_GetItem(meta_path, i);
            loader = PyObject_CallMethod(hook, "find_module",
                                         "sO", fullname,
                                         path != NULL ?
                                         path : Py_None);
            if (loader == NULL) {
                Py_DECREF(meta_path);
                goto error_exit;  /* true error */
            }
            if (loader != Py_None) {
                /* a loader was found */
                *p_loader = loader;
                Py_DECREF(meta_path);
                PyMem_FREE(name);
                return &importhookdescr;
            }
            Py_DECREF(loader);
        }
        Py_DECREF(meta_path);
    }

    if (path == NULL) {
        // path为NULL,从默认路径开始查找
        if (is_builtin(name)) {
            // 检查是不是内建模块
            strcpy(buf, name);
            PyMem_FREE(name);
            return &fd_builtin;
        }
        if ((find_frozen(name)) != NULL) {
            strcpy(buf, name);
            PyMem_FREE(name);
            return &fd_frozen;
        }

        // path = sys.path
        path = PySys_GetObject("path");
    }

    npath = PyList_Size(path);
    namelen = strlen(name);
    // 遍历所有的path
    for (i = 0; i < npath; i++) {
        PyObject *copy = NULL;
        PyObject *v = PyList_GetItem(path, i);
        strcpy(buf, PyString_AS_STRING(v));
          ....
        strcpy(buf+len, name);
        len += namelen;
        // 做完上面这一步后,此时buf存储是import的全路径

        /* Check for package import (buf holds a directory name,
           and there's an __init__ module in that directory */
        if (isdir(buf) &&         /* it's an existing directory */ // 是不是存在的目录
            case_ok(buf, len, namelen, name)) { /* case matches */
            if (find_init_module(buf)) { /* and has __init__.py */
                Py_XDECREF(copy);
                PyMem_FREE(name);
                return &fd_package;
            }
            else {
                char warnstr[MAXPATHLEN+80];
                sprintf(warnstr, "Not importing directory "
                    "'%.*s': missing __init__.py",
                    MAXPATHLEN, buf);
                if (PyErr_Warn(PyExc_ImportWarning,
                               warnstr)) {
                    Py_XDECREF(copy);
                    goto error_exit;
                }
            }
        }
        for (fdp = _PyImport_Filetab; fdp->suffix != NULL; fdp++) {
            ...
            fp = fopen(buf, filemode);
            if (fp != NULL) {
                if (case_ok(buf, len, namelen, name)) {
                    //printf("in find_module case_ok buf is %s\n", buf);
                    break;
                }
                else {                   /* continue search */
                    fclose(fp);
                    fp = NULL;
                }
            }
        }
    *p_fp = fp;
    PyMem_FREE(name);
    return fdp;
}

find_module的基本流程可以描述如下:

  1. 尝试在sys.meta_path里面找到hook class,并尝试获得loader。如果成功,函数返回
  2. 如果path是空:
    先看要import得module是不是builtin module,是,返回
    再看module是不是frozen module,是,返回
    然后将path = sys.path,从默认路径开始查找
  3. 开始遍历path了
    创建full为import的全路径
    分两种情况
    1) full是一个目录 package
    2) full是一个module
    

3.load_module函数

load_module函数并不复杂,其根据不同的类型选择不同的加载module的方式。

static PyObject *
load_module(char *name, FILE *fp, char *pathname, int type, PyObject *loader)
{
    switch (type) {
    // .py文件
    case PY_SOURCE:
        m = load_source_module(name, pathname, fp);
        break;
    // .pyc文件
    case PY_COMPILED:
        m = load_compiled_module(name, pathname, fp);
        break;
    // .so or .dll or .pyd 文件
    case C_EXTENSION:
        m = _PyImport_LoadDynamicModule(name, pathname, fp);
        break;
    // package
    case PKG_DIRECTORY:
        m = load_package(name, pathname);
        break;
        ....
    }

    return m;
}