module
目录
相关位置文件
- cpython/Objects/moduleobject.c
- cpython/Include/moduleobject.h
- cpython/Objects/clinic/moduleobject.c.h
- cpython/Python/import.c
- cpython/Python/clinic/import.c.h
- cpython/Lib/importlib/_bootstrap.py
- cpython/Lib/importlib/_bootstrap_external.py
内存构造
Include/moduleobject.h 中定义了一个 c struct, 名为 PyModuleDef

Objects/moduleobject.c 中又定义了 PyModuleObject 这个结构, 它里面有个类型为 PyModuleDef 的字段

示例
字段 md_dict 是该 module 对象的 __dict__ 属性
PyModuleDef 是可选的, m_base 字段中的 index 值是用来在 sys.modules 中以下标的方式搜索该模块用的, 而不需要遍历整个数组匹配名称来搜索
m_size 存储了每个 module 需要使用到的额外数据的大小
m_clear 和 m_free 是在 module 对象 删除/释放时和 c++ 中析构函数的作用类似
更多详情请参考 PEP 3121 – Extension Module Initialization and Finalization
import _locale

import re

如何调试 import
当你尝试到源码里面看看 import 是如何工作时
跟着函数的调用栈

你可以发现, 核心的函数是 interp->importlib, 这个函数是在下面这个位置初始化的
/* cpython/Python/pylifecycle.c */
static _PyInitError
initimport(PyInterpreterState *interp, PyObject *sysmod)
{
/* 忽略 */
importlib = PyImport_AddModule("_frozen_importlib");
if (importlib == NULL) {
return _Py_INIT_ERR("couldn't get _frozen_importlib from sys.modules");
}
interp->importlib = importlib;
Py_INCREF(interp->importlib);
/* 忽略 */
}
搜索 _frozen_importlib, 你可以找到一个几乎是二进制的文件 Python/importlib.h, 里面的内容如下
/* 被 Programs/_freeze_importlib.c 这个程序自动生成的 */
const unsigned char _Py_M__importlib_bootstrap[] = {
99,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,
...
}
*/
我们发现 _freeze_importlib.c 编译后的程序会把 Lib/importlib/_bootstrap.py 中的 python 代码转换为 二进制格式并存储在 Python/importlib.h 中

到这里我们发现了一个比较有意思的地方, 整个 import 的流程其实大致上是用 python 代码完成的, interp->importlib 中的 PyId__find_and_load 属性能映射到 Lib/importlib/_bootstrap.py 文件中的 _find_and_load python 函数
当你更改了 Lib/importlib/_bootstrap.py 中的代码时, 你需要重新生成 Python/importlib.h 这个文件, 并重新编译生成一个新的可执行程序 python.exe
# 编译 ./Programs/_freeze_importlib.c, 用编译后的程序去做下面的转换
# _bootstrap_external.py -> importlib_external.h
# _bootstrap.py -> importlib.h
# zipimport.py -> importlib_zipimport.h
make regen-importlib
# 重新编译生成新的可执行 `python.exe`
make
import 是如何实现的
我们用 dis 模块处理一个只有一行代码 import _locale 的脚本看看
./python.exe -m dis test.py
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (_locale)
6 STORE_NAME 0 (_locale)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
这里核心的 opcde 是 IMPORT_NAME
把所有的 IMPORT_NAME 调用到的并且相关的代码拷贝过来并写上注释看起来也很让人头大, 这里不这么做
想象以下当前有两个以上的线程在导入同一个 _locale 对象, CPython 如何处理这种情况 ?
如果你读了源代码和下面的图片, 你会看到这里使用了锁机制来防止竞争的情况发生
整个过程大概如下
- opcode
IMPORT_NAME会检查 sys.module 中是否包含有要导入的模块的名称, 如果有直接返回这个对象 - 尝试获得
_imp这把锁 - 获取到
_module_locks里面对应模块名称的值, 如果名称不在_module_locks里, 则创建一个新的对象 (position 1的位置) - 对步骤 3 获得的对象上锁 (在
position 2的位置) - 释放
_imp上的锁 (在position 3的位置) - 再次检查是否即将导入的名称在 sys.module 中, 如果在释放
_module_locks获得的锁并结束 (在position 4的位置) - 对于
sys.meta_path中的每个finder, 如果finder能成功导入, 则释放_module_locks获得的锁并结束 - 抛出无法找到的异常
在 position 1, 只有获得了 _imp 上面的锁的线程可以更改 _module_locks 里的值, 当前的线程会检查当前要导入的名称在不在 _module_locks 这个字典中, 如果不在就往这个字典插入一个名称为当前模块, 值为锁对象
在 position 3, _imp 已经被释放了, 如果其他的线程在尝试导入其他的模块, 这里是不会相互影响的, 其他的线程可以获得 _imp 这把锁并继续同样的操作, 如果其他的线程尝试导入相同的模块, 即使获得了 _imp 锁, 获取 _module_locks 里面的锁的时候也会失败, 因为当前的线程已经拥有了这个锁了, 某种意义上, _module_locks 中存储的是比 _imp 更细粒度的锁

在 position 4 的位置, 当前的线程会再检查一遍 sys.modules
在 position 5 的位置, 每一个 finder.find 之前都会获得 _imp 锁, 并在 finder.find 之后释放掉

finder
默认情况下 sys.meta_path 只有 3 个不同的 finder
>>> sys.meta_path
[<class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>]
BuiltinImporter
BuiltinImporter 会处理所有的 built-in 模块, 比如当我们运行
import _locale
BuiltinImporter 定义的位置是在 Lib/importlib/_bootstrap.py, BuiltinImporter.find_spec 会返回一个 ModuleSpec 对象, ModuleSpec 有一个 loader 属性, 在返回的时候这个 loader 已经和 BuiltinImporter 绑定在一起了
之后 ModuleSpec.loader.create_module 就会被调用, 最终调用到了 _imp_create_builtin 这个 c 函数上
/* 在 cpython/Python/import.c 这个位置 */
static PyObject *
_imp_create_builtin(PyObject *module, PyObject *spec)
{
/* 一些类型检查 */
PyObject *modules = NULL;
for (p = PyImport_Inittab; p->name != NULL; p++) {
/* PyImport_Inittab 是一个 c 数组, 每一个元素都是一个 built-in module 名称 和 初始化的 c 函数
这里的 for 循环遍历 PyImport_Inittab 这个数组, 检查他们的名称和你提供的名称是否匹配,
如果匹配直接调用对应的初始化的 c 函数并返回m*/
PyModuleDef *def;
if (_PyUnicode_EqualToASCIIString(name, p->name)) {
if (p->initfunc == NULL) {
/* Cannot re-init internal module ("sys" or "builtins") */
mod = PyImport_AddModule(namestr);
Py_DECREF(name);
return mod;
}
mod = (*p->initfunc)();
/* 一些类型检查 */
}
}
Py_DECREF(name);
Py_RETURN_NONE;
}
/* in cpython/PC/config.c
struct _inittab _PyImport_Inittab[] = {
{"_abc", PyInit__abc},
{"array", PyInit_array},
{"_ast", PyInit__ast},
{"audioop", PyInit_audioop},
{"binascii", PyInit_binascii},
{"cmath", PyInit_cmath},
{"errno", PyInit_errno},
{"faulthandler", PyInit_faulthandler},
{"gc", PyInit_gc},
{"math", PyInit_math},
{"nt", PyInit_nt}, /* Use the NT os functions, not posix */
{"_operator", PyInit__operator},
{"_signal", PyInit__signal},
{"_md5", PyInit__md5},
...
}
*/
FrozenImporter
FrozenImporter.loader.create_module 的定义, 调用方式等和 BuiltinImporter 类似
int
PyImport_ImportFrozenModuleObject(PyObject *name)
{
p = find_frozen(name);
/* 类型检查 */
co = PyMarshal_ReadObjectFromString((const char *)p->code, size);
/* 类型检查 */
d = module_dict_for_exec(name);
/* 类型检查 */
m = exec_code_in_module(name, d, co);
if (m == NULL)
goto err_return;
Py_DECREF(co);
Py_DECREF(m);
return 1;
err_return:
Py_DECREF(co);
return -1;
}
static const struct _frozen *find_frozen(PyObject *name)
{
/* 类型检查 */
for (p = PyImport_FrozenModules; ; p++) {
/* 遍历 PyImport_FrozenModules 这个 c 数组检查是否有名称匹配的对象 */
if (p->name == NULL)
return NULL;
if (_PyUnicode_EqualToASCIIString(name, p->name))
break;
}
return p;
}
/* 在 cpython/Python/frozen.c 这个位置
static const struct _frozen _PyImport_FrozenModules[] = {
/* importlib */
{"_frozen_importlib", _Py_M__importlib_bootstrap,
(int)sizeof(_Py_M__importlib_bootstrap)},
{"_frozen_importlib_external", _Py_M__importlib_bootstrap_external,
(int)sizeof(_Py_M__importlib_bootstrap_external)},
{"zipimport", _Py_M__zipimport,
(int)sizeof(_Py_M__zipimport)},
/* Test module */
{"__hello__", M___hello__, SIZE},
/* Test package (negative size indicates package-ness) */
{"__phello__", M___hello__, -SIZE},
{"__phello__.spam", M___hello__, SIZE},
{0, 0, 0} /* sentinel */
};
const struct _frozen *PyImport_FrozenModules = _PyImport_FrozenModules;
*/
PathFinder
PathFinder.loader.create_module 会最终调用 _new_module 返回, 而不是像上面的调用 create_module 返回, 外层函数定义的位置在 cpython/Lib/importlib/_bootstrap.py, _new_module 不会调用 c 函数, create_module 会调用对应的 c 函数
def module_from_spec(spec):
"""通过提供的 spec 生成一个 module 对象"""
# 典型的 loader 是不会提供 create_module() 函数的
module = None
if hasattr(spec.loader, 'create_module'):
# BuiltinImporter and FrozenImporter 会在这里调用 create_module
# 最后跑到他们的 c function 中去
module = spec.loader.create_module(spec)
elif hasattr(spec.loader, 'exec_module'):
raise ImportError('loaders that define exec_module() '
'must also define create_module()')
if module is None:
# PathFinder 会到达这里
# _new_module 简单的返回了一个基本的 module 类型对象
# 加载的过程都是通过参数 spec 完成的
module = _new_module(spec.name)
_init_module_attrs(spec, module)
return module
def _new_module(name):
return type(sys)(name)
PathFinder.find_spec 会把 sys.path_hooks 中的对象取出来, 并按 sys.path_hooks 中的对象的顺序, 把每个对象作为 finder 去尝试进行模块导入
FileFinder 会被作为默认的 path_hooks, 你可以在 cpython/Lib/importlib/_bootstrap_external.py 中找到下面这段代码
def _install(_bootstrap_module):
"""Install the path-based import components."""
_setup(_bootstrap_module)
supported_loaders = _get_supported_file_loaders()
sys.path_hooks.extend([FileFinder.path_hook(*supported_loaders)])
sys.meta_path.append(PathFinder)
FileFinder.find_spec 会处理搜索过程, 和操作系统的文件系统交互, 并且把这些文件状态缓存下来, 当文件变更时通过缓存中的状态是可以被感知到的
当你想定义你自己的 Finder 时, 或者更改系统默认的 import 的行为, 你也可以更改 sys.path_hooks 这个变量