大师兄的Python源码学习笔记(三十五): 模块的动态加载机制(二)
大师兄的Python源码学习笔记(三十七): 模块的动态加载机制(四)
三、import机制的实现
- 通过黑盒探索,可以发现Python的import机制可分为三个不同的功能:
- Python运行时的全局module pool的维护和搜索;
- 解析与搜索module路径的树状结构;
- 对不同文件格式的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函数是核心,它的功能是将args和kwds中所包含的所有对象按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动作上锁和解锁,目的是为了同步不同的线程对同一个module的import动作。
- 代码中的fromlist通常是Py_None,但当Python虚拟机进行类似
from x impory y,z
这样的动作时,fromlist就成为一个诸如(y,z)这样的PyTupleObject对象。