这一篇接着上一个篇继续讨论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的基本流程可以描述如下:
- 尝试在sys.meta_path里面找到hook class,并尝试获得loader。如果成功,函数返回
- 如果path是空:
先看要import得module是不是builtin module,是,返回
再看module是不是frozen module,是,返回
然后将path = sys.path,从默认路径开始查找 - 开始遍历path了
创建full为import的全路径
分两种情况
1) full是一个目录 package2) 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;
}