- 大师兄的Python源码学习笔记(三十五): 模块的动态加载机制
- 大师兄的Python源码学习笔记(三十七): 模块的动态加载机制
- 大师兄的Python源码学习笔记(三十六): 模块的动态加载机制
- 大师兄的Python源码学习笔记(三十九): Python的多线
- 大师兄的Python源码学习笔记(三十八): 模块的动态加载机制
- 大师兄的Python源码学习笔记(三十四): 模块的动态加载机制
- 大师兄的Python源码学习笔记(四十一): Python的多线
- 大师兄的Python源码学习笔记(四十): Python的多线程
- 大师兄的Python源码学习笔记(五十一): Python的内存
- 大师兄的Python源码学习笔记(四十四): Python的多线
大师兄的Python源码学习笔记(三十三): 运行环境初始化(五)
大师兄的Python源码学习笔记(三十五): 模块的动态加载机制(二)
- 前面的章节聚焦在一个模块内,但现实程序中通常有多个模块。
- 因此模块间的引用和交互也是程序中的重要组成部分。
一、从import案例开始
- 创建一个import案例,并查看对应的指令字节码:
demo.py
import os
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (os)
6 STORE_NAME 0 (os)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
- LOAD_CONST和STORE_NAME指令已经很熟悉了,可以看出与import对应的是字节码指令IMPORT_NAME。
- IMPORT_NAME在虚拟机中对应的代码如下:
Python\ceval.c
TARGET(IMPORT_NAME) {
PyObject *name = GETITEM(names, oparg);
PyObject *fromlist = POP();
PyObject *level = TOP();
PyObject *res;
res = import_name(f, name, fromlist, level);
Py_DECREF(level);
Py_DECREF(fromlist);
SET_TOP(res);
if (res == NULL)
goto error;
DISPATCH();
}
- 这里的核心是调用import_name函数,并传入了f、name、from_list和level四个参数:
- f:帧栈(FrameObject)。
- name:一个PyUnicodeObject对象,表示调用模块的名字。
- from_list: 为None,对应
2 LOAD_CONST 1 (None)
。- level: 为0,对应
0 LOAD_CONST 0 (0)
。
Python\ceval.c
static PyObject *
import_name(PyFrameObject *f, PyObject *name, PyObject *fromlist, PyObject *level)
{
_Py_IDENTIFIER(__import__);
PyObject *import_func, *res;
PyObject* stack[5];
import_func = _PyDict_GetItemId(f->f_builtins, &PyId___import__);
if (import_func == NULL) {
PyErr_SetString(PyExc_ImportError, "__import__ not found");
return NULL;
}
/* Fast path for not overloaded __import__. */
if (import_func == PyThreadState_GET()->interp->import_func) {
int ilevel = _PyLong_AsInt(level);
if (ilevel == -1 && PyErr_Occurred()) {
return NULL;
}
res = PyImport_ImportModuleLevelObject(
name,
f->f_globals,
f->f_locals == NULL ? Py_None : f->f_locals,
fromlist,
ilevel);
return res;
}
Py_INCREF(import_func);
stack[0] = name;
stack[1] = f->f_globals;
stack[2] = f->f_locals == NULL ? Py_None : f->f_locals;
stack[3] = fromlist;
stack[4] = level;
res = _PyObject_FastCall(import_func, stack, 5);
Py_DECREF(import_func);
return res;
}
- import_name函数首先通过_Py_IDENTIFIER获取内建函数__import__并将其包装为可执行的PyCFunctionObject对象:
/* This structure helps managing static strings. The basic usage goes like this:
Instead of doing
r = PyObject_CallMethod(o, "foo", "args", ...);
do
_Py_IDENTIFIER(foo);
...
r = _PyObject_CallMethodId(o, &PyId_foo, "args", ...);
PyId_foo is a static variable, either on block level or file level. On first
usage, the string "foo" is interned, and the structures are linked. On interpreter
shutdown, all strings are released (through _PyUnicode_ClearStaticStrings).
Alternatively, _Py_static_string allows choosing the variable name.
_PyUnicode_FromId returns a borrowed reference to the interned string.
_PyObject_{Get,Set,Has}AttrId are __getattr__ versions using _Py_Identifier*.
*/
typedef struct _Py_Identifier {
struct _Py_Identifier *next;
const char* string;
PyObject *object;
} _Py_Identifier;
#define _Py_static_string_init(value) { .next = NULL, .string = value, .object = NULL }
#define _Py_static_string(varname, value) static _Py_Identifier varname = _Py_static_string_init(value)
#define _Py_IDENTIFIER(varname) _Py_static_string(PyId_##varname, #varname)
- 接着判断__import__是否被重载,如果没有,则调用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;
if (name == NULL) {
PyErr_SetString(PyExc_ValueError, "Empty module name");
goto error;
}
/* The below code is importlib.__import__() & _gcd_import(), ported to C
for added performance. */
if (!PyUnicode_Check(name)) {
PyErr_SetString(PyExc_TypeError, "module name must be a string");
goto error;
}
if (PyUnicode_READY(name) < 0) {
goto error;
}
if (level < 0) {
PyErr_SetString(PyExc_ValueError, "level must be >= 0");
goto error;
}
if (level > 0) {
abs_name = resolve_name(name, globals, level);
if (abs_name == NULL)
goto error;
}
else { /* level == 0 */
if (PyUnicode_GET_LENGTH(name) == 0) {
PyErr_SetString(PyExc_ValueError, "Empty module name");
goto error;
}
abs_name = name;
Py_INCREF(abs_name);
}
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);
}
error:
Py_XDECREF(abs_name);
Py_XDECREF(mod);
Py_XDECREF(package);
if (final_mod == NULL)
remove_importlib_frames();
return final_mod;
}
- 在这里会用到level参数,如果level大于0,则在响应的父目录寻找abs_name。
- 如果level等于0,则将abs_name赋值为name。
- 在Python中,一个模块被导入后,会加入到sys.modules中,并首先从sys.modules中查找模块,这就是为什么Python的模块只会被加载一次。
- 接着,经过一系列判断,虚拟机调用PyImport_GetModule获取模块。
Python\import.c
PyObject *
PyImport_GetModule(PyObject *name)
{
PyObject *m;
PyObject *modules = PyImport_GetModuleDict();
if (modules == NULL) {
PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules");
return NULL;
}
Py_INCREF(modules);
if (PyDict_CheckExact(modules)) {
m = PyDict_GetItemWithError(modules, name); /* borrowed */
Py_XINCREF(m);
}
else {
m = PyObject_GetItem(modules, name);
if (m == NULL && PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Clear();
}
}
Py_DECREF(modules);
return m;
}
- 最后调用_PyObject_CallMethodIdObjArgs函数,导入模块,并通过STORE_NAME将其压入栈中:
Objects\call.c
PyObject *
_PyObject_CallMethodIdObjArgs(PyObject *obj,
struct _Py_Identifier *name, ...)
{
va_list vargs;
PyObject *callable, *result;
if (obj == NULL || name == NULL) {
return null_error();
}
callable = _PyObject_GetAttrId(obj, name);
if (callable == NULL) {
return NULL;
}
va_start(vargs, name);
result = object_vacall(callable, vargs);
va_end(vargs);
Py_DECREF(callable);
return result;
}
网友评论