CPython 模块对象 C API 详解:PyModule_Type、PyModuleDef、Slot 定义与模块状态管理
本文基于 CPython 官方文档 Module Objects 展开,系统讲解 CPython C API 中“模块对象”这一核心主题:PyModule_Type 类型对象、PyModule_* 查询与创建函数、3.15 起推荐的 PySlot 数组(slots)模块定义方式、传统的 PyModuleDef 结构体方式、模块状态(module state)的生命周期管理、模块 token 校验,以及模块初始化用的辅助函数(PyModule_AddObject、PyModule_AddFunctions 等)。读完本文,你可以在 C 扩展中正确创建模块、声明模块级函数与状态、支持子解释器(sub-interpreters)与 free-threaded 构建,并理解这些 API 在 Objects/moduleobject.c 与 Include/moduleobject.h 中的实际实现。
1. 模块对象与 PyModule_Type
Python 中每个可导入的单元都是一个模块对象。CPython 为其定义了专门的类型:
PyTypeObject PyModule_Type:代表 Python 模块类型的PyTypeObject实例,对 Python 程序而言暴露为types.ModuleType。
两个最常用的类型检查宏,其定义见 Include/moduleobject.h:
| 函数/宏 | 语义 |
|---|---|
int PyModule_Check(PyObject *p) |
若 p 是模块对象或模块对象的子类型则返回真,恒不失败 |
int PyModule_CheckExact(PyObject *p) |
若 p 是“精确”的模块对象(PyModule_Type 本身,而非子类型)则返回真,恒不失败 |
从源码看,两者都只是对类型指针的宏封装:PyModule_Check(op) 展开为 PyObject_TypeCheck((op), &PyModule_Type)(允许子类型),而 PyModule_CheckExact(op) 展开为 Py_IS_TYPE((op), &PyModule_Type)(要求精确类型)。注意 PyModule_CheckExact 的文档表述为“是模块对象、但不是 PyModule_Type 的子类型”,这一点容易误读,写代码时应以“精确类型检查”为准。
2. 模块的创建与查询 API
2.1 创建模块:PyModule_NewObject / PyModule_New
| 函数 | 说明 |
|---|---|
PyObject *PyModule_NewObject(PyObject *name) |
创建一个新模块对象并设置其 __name__ 为 name;同时填充 __doc__、__package__、__loader__(除 __name__ 外均设为 None)。调用方负责自行设置 __file__。出错时返回 NULL 并设置异常。3.3 新增;3.4 起 __package__ 与 __loader__ 才被设为 None |
PyObject *PyModule_New(const char *name) |
与 PyModule_NewObject 相同,但 name 是 UTF-8 编码的 C 字符串而非 Unicode 对象 |
实现位于 Objects/moduleobject.c:PyModule_New 先用 PyUnicode_FromString 把 UTF-8 名称转为 Unicode 对象,再转调 PyModule_NewObject。PyModule_NewObject 内部经由 new_module_notrack() 分配对象,md_dict = PyDict_New() 创建命名空间字典,再由 module_init_dict() 一次性写入 __name__、__doc__、__package__、__loader__、__spec__ 等内置属性(doc 为 NULL 时写 Py_None)。这与文档描述一致:__file__ 不由创建函数设置,而是留给调用方(或导入机制)填充。
2.2 查询模块属性
| 函数 | 返回值与错误 |
|---|---|
PyObject *PyModule_GetDict(PyObject *module) |
返回实现模块命名空间的字典对象,与模块的 __dict__ 属性相同。若参数不是模块对象则抛出 SystemError 并返回 NULL。返回的是借用的引用,模块销毁前有效 |
PyObject *PyModule_GetNameObject(PyObject *module)(3.3+) |
返回模块的 __name__;若未定义或不是字符串,抛 SystemError 并返回 NULL |
const char *PyModule_GetName(PyObject *module) |
同上,但返回 'utf-8' 编码的 C 字符串。缓冲区仅在模块被重命名或销毁前有效;注意 Python 代码可以通过设置 __name__ 属性重命名模块 |
PyModuleDef *PyModule_GetDef(PyObject *module) |
返回模块所由创建的 PyModuleDef 结构体指针;若不是从定义创建的则返回 NULL。出错时也返回 NULL,需配合 PyErr_Occurred() 区分“无定义”与“出错” |
PyObject *PyModule_GetFilenameObject(PyObject *module)(3.2+) |
通过模块的 __file__ 属性返回模块加载来源文件的名称(Unicode 对象);未定义或不是字符串时抛 SystemError |
const char *PyModule_GetFilename(PyObject *module) |
返回 UTF-8 编码的文件名,仅在 __file__ 被重新赋值或模块销毁前有效。3.2 起弃用:不可编码的文件名会引发 UnicodeEncodeError,应改用 PyModule_GetFilenameObject |
文档明确建议:扩展应优先使用其他 PyModule_* 与 PyObject_* 函数,而不是直接操纵模块的 __dict__(即避免依赖 PyModule_GetDict 拿到的裸字典)。
3. 模块定义:从 PyModuleDef 到 Slot 数组
CPython 中“如何创建一个模块”的描述方式有两种:slots 数组(3.15 起的推荐方式)与 PyModuleDef 结构体(传统方式,仍受支持、暂无移除计划)。
3.15+ 推荐: PySlot 数组(Py_mod_name / Py_mod_abi / Py_mod_exec / ...)
│
▼
PyModule_FromSlotsAndSpec() + PyModule_Exec()
传统方式: static PyModuleDef mymoduledef = { ... }
│
▼
PyModule_Create() / PyModule_FromDefAndSpec()
(m_slots 允许重复的 Py_mod_exec,按出现顺序执行)
3.1 Slot 数组总述
用 C API 创建的模块,通常用一个 PySlot 结构体数组来定义,该数组是“如何创建模块”的描述(slots 机制的通用说明见 Slots)。slots 数组一般用于定义扩展模块的“主”模块对象(参见 extension-modules),也可用于动态创建扩展模块(见第 6 节)。
规则:除非另有说明,同一个 slot ID 在数组中不允许重复。 这与传统 PyModuleDef.m_slots 形成对比——后者的 m_slots 数组可以包含多个 Py_mod_exec 槽,它们按出现顺序处理(为向后兼容而保留的特例)。
3.2 元数据 slots
| Slot ID | 值类型 | 说明 |
|---|---|---|
Py_mod_name |
NUL 结尾的 UTF-8 const char * |
新模块的名称。模块通常经由 importlib.machinery.ModuleSpec 创建,此时会优先采用 spec 中的名称;但仍建议保留此槽以便内省与调试。3.15 新增,旧版本请使用 PyModuleDef.m_name |
Py_mod_doc |
NUL 结尾的 UTF-8 const char * |
模块的 docstring,通常设置为由 PyDoc_STRVAR 创建的变量。3.15 新增,旧版本请使用 PyModuleDef.m_doc |
3.3 特性 slots
| Slot ID | 取值 | 说明 |
|---|---|---|
Py_mod_abi |
指向 PyABIInfo 结构 |
描述扩展所使用的 ABI;用 PyABIInfo_VAR(abi_info); 宏定义合适变量后以 PySlot_DATA(Py_mod_abi, &abi_info) 提供。创建模块时 Python 会用 PyABIInfo_Check 校验该槽。除从 PyModuleDef 创建的模块外,此槽是必填的。3.15 新增 |
Py_mod_multiple_interpreters |
NULL / Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED / Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED / Py_MOD_PER_INTERPRETER_GIL_SUPPORTED |
决定该模块能否被子解释器导入:NOT_SUPPORTED 表示不支持;SUPPORTED 表示支持,但仅限共享主解释器 GIL 的情况;PER_INTERPRETER_GIL_SUPPORTED 表示即使子解释器拥有独立 GIL 也可导入(参见 isolating-extensions-howto)。未指定时默认 Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED。3.12 新增 |
Py_mod_gil |
NULL / Py_MOD_GIL_USED / Py_MOD_GIL_NOT_USED |
GIL_USED 表示模块依赖 GIL 存在、可能无同步地访问全局状态;GIL_NOT_USED 表示模块在无 GIL 环境下安全。未启用 --disable-gil 的构建会忽略该槽;否则它决定导入该模块是否会自动启用 GIL。未指定时默认 Py_MOD_GIL_USED。3.13 新增,适用于 free-threaded 构建 |
后两个槽的取值出于历史原因被声明为指针(void *)。在 PySlot 数组中应使用 PySlot_DATA 提供:
PySlot_DATA(Py_mod_multiple_interpreters,
Py_MOD_PER_INTERPRETER_GIL_SUPPORTED)
PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED)
这些取值宏在源码 Include/moduleobject.h 中定义,并被 Py_LIMITED_API 版本门控:Py_MOD_MULTIPLE_INTERPRETERS_* 要求 limited API 不低于 0x030c0000(3.12),Py_MOD_GIL_* 要求不低于 0x030d0000(3.13)——与文档的 versionadded 标注精确对应。
3.4 创建与初始化 slots
| Slot ID | 函数签名 | 说明 |
|---|---|---|
Py_mod_create |
PyObject *create_module(PyObject *spec, PyModuleDef *def) |
创建模块对象本身的函数。spec 是“ModuleSpec-like”对象:importlib.machinery.ModuleSpec 定义的属性具有相同语义,但任意属性都可能缺失;def 为 NULL 或(从定义创建时的)模块定义。应返回新模块对象,或设置错误并返回 NULL。该函数应尽可能精简,尤其不要调用任意 Python 代码——再次导入同一模块可能引发死循环。未指定时,导入机制会用 PyModule_New 创建普通模块对象,名称取自 spec 而非定义(这样扩展模块可随符号链接在不同名称下导入而共享同一份定义)。返回对象不要求是 PyModule_Type 的实例,但 Py_mod_exec、模块状态槽(Py_mod_state_*)与 Py_mod_token 仅对 PyModule_Type 实例可用。3.5 新增;3.15 起 spec 可以是 ModuleSpec-like 对象,def 可为 NULL |
Py_mod_exec |
int exec_module(PyObject *module) |
“执行/初始化”模块的函数,等价于执行 Python 模块的代码,通常用来向模块添加类与常量。3.5 新增;3.15 起禁止重复 Py_mod_exec 槽(PyModuleDef.m_slots 例外) |
Py_mod_methods |
PyMethodDef 数组 |
模块级函数表,可作为 PyModule_AddFunctions 的 functions 参数。slots 数组中只允许出现一个 Py_mod_methods;若要从多个 PyMethodDef 数组添加函数,应在 Py_mod_exec 中直接调用 PyModule_AddFunctions。函数表必须静态分配(或保证比模块对象活得更久)。3.15 新增,旧版本请使用 PyModuleDef.m_methods |
4. 模块状态(Module State)
扩展模块可以拥有模块状态——在模块创建时分配、模块对象释放时释放的一块内存。典型用途是保存异常类型,或模块定义的任何类型对象。模块状态通过专门的 slots 指定(见下)。
关键特性与设计理由:
- 与模块的 Python 属性不同,Python 代码无法替换或删除存储在模块状态中的数据;
- 把逐模块信息放在属性与模块状态中(而非静态全局变量),使模块对象相互隔离、更适合在多个子解释器中使用,也有助于解释器关闭时做有序清理;
- 若模块状态中保存了对 Python 对象的引用,必须实现
Py_mod_state_traverse与Py_mod_state_clear,否则会引用泄漏。
从源码结构看(Objects/moduleobject.c 的 assert_def_missing_or_redundant),对由 PyModuleDef 创建的模块,调试构建会断言 m_size/m_traverse/m_clear/m_free 在模块对象与静态定义之间完全一致(md_state_size == def->m_size 等),说明 CPython 在创建时把定义中的状态信息“复制”进了模块对象本身。
4.1 获取模块状态的 API
void *PyModule_GetState(PyObject *module);
/* 返回模块创建时分配的“状态”内存块指针,或 NULL(见 Py_mod_state_size)。
出错时返回 NULL 并设置异常;用 PyErr_Occurred() 区分
“无模块状态”与“出错”。 */
int PyModule_GetStateSize(PyObject *module, Py_ssize_t *result);
/* 3.15 新增。将模块状态的大小(由 Py_mod_state_size 或
PyModuleDef.m_size 指定)写入 *result 并返回 0;
出错时 *result 设为 -1,返回 -1 并设置异常。 */
4.2 定义模块状态的 slots
| Slot ID | 函数签名 | 说明 |
|---|---|---|
Py_mod_state_size |
— | 模块状态占用的字节数。设置为非负值表示模块可被重新初始化,并声明其所需的额外状态内存。详见 PEP 3121。3.15 新增,旧版本请使用 PyModuleDef.m_size |
Py_mod_state_traverse |
int traverse_module_state(PyObject *module, visitproc visit, void *arg) |
GC 遍历模块对象时调用的遍历函数,参数含义类似 PyTypeObject.tp_traverse。若状态尚未分配(模块刚创建、Py_mod_exec 执行之前),不会调用它——精确地说,当状态大小(Py_mod_state_size)大于 0 而 PyModule_GetState() 返回 NULL 时不调用。3.15 新增,旧版本请用 PyModuleDef.m_traverse |
Py_mod_state_clear |
int clear_module_state(PyObject *module) |
GC 清理模块对象时调用的清理函数。调用时机限制同上。与 PyTypeObject.tp_clear 一样,它并不总是在模块释放前被调用:例如引用计数足以判定对象不再使用时,循环 GC 不参与,会直接调用 Py_mod_state_free。3.15 新增,旧版本请用 PyModuleDef.m_clear |
Py_mod_state_free |
int free_module_state(PyObject *module) |
模块对象释放(deallocation)时调用的函数。调用时机限制同上。3.15 新增,旧版本请用 PyModuleDef.m_free |
5. 模块 Token:验证“模块归属”
每个模块都可以关联一个 token:一个指针大小的值,用来标识模块状态的内存布局。当你拿到一个模块对象、但不确定它是否“属于”你的扩展时,可这样检查(文档给出的代码示例):
PyObject *module = <待检查的模块>;
void *module_token;
if (PyModule_GetToken(module, &module_token) < 0) {
return NULL;
}
if (module_token != your_token) {
PyErr_SetString(PyExc_ValueError, "unexpected module");
return NULL;
}
// 该模块的状态具有预期的内存布局,可以安全地强转
struct my_state *state = (struct my_state *)PyModule_GetState(module);
模块的 token —— 也就是上例中的 your_token 取值 —— 取决于模块的创建方式:
- 用
PyModuleDef创建的模块:token 是该PyModuleDef的地址; - 用
Py_mod_token槽定义的模块:token 是该槽的值; - 由
PyModExport_*导出钩子 创建的模块:token 是导出钩子返回的 slots 数组(除非被Py_mod_token覆盖)。
| API | 说明 |
|---|---|
Py_mod_token 槽 |
3.15 新增。若用它设置 token,必须保证:该指针比模块对象活得更久,不会在他处被复用;它“属于”定义该类的扩展模块,不会与其他扩展冲突;若 token 指向某个 PyModuleDef 结构体,则模块必须表现得如同从该定义创建的一样,特别是模块状态必须有匹配的布局与语义。注意:从 PyModuleDef 创建的模块永远使用该定义结构的地址作为 token,因此 Py_mod_token 不能用在 PyModuleDef.m_slots 中 |
int PyModule_GetToken(PyObject *module, void **result) |
3.15 新增。将 module 的模块 token 写入 *result 并返回 0;出错时 *result 设为 NULL,返回 -1 并设置异常 |
另可参考 PyType_GetModuleByToken 的反向查询。
6. 动态创建扩展模块(3.15+)
以下两个函数可用于动态创建扩展模块,而不依赖扩展自身的 export hook:
PyObject *PyModule_FromSlotsAndSpec(const PySlot *slots, PyObject *spec);
/* 3.15 新增。根据 slots 数组与 ModuleSpec 创建新模块对象。
- slots 必须指向以 slot ID 为 0 的条目(通常写作 PySlot_END)结尾的
PySlot 数组,且必须包含 Py_mod_abi 条目。
- spec 可以是 Py_mod_create 文档所述的任意 ModuleSpec-like 对象;
目前 spec 必须带有 name 属性。
- 成功返回新模块;出错返回 NULL 并设置异常。
- 注意:它不会处理执行槽 Py_mod_exec。
必须同时调用 PyModule_FromSlotsAndSpec 与 PyModule_Exec
才能完成模块的完整初始化(参见 multi-phase-initialization)。 */
int PyModule_Exec(PyObject *module);
/* 3.15 新增。执行 module 的 Py_mod_exec 槽。
成功返回 0;出错返回 -1 并设置异常。
若 module 没有 slots(例如使用传统单阶段初始化),
本函数什么都不做并返回 0。 */
实现入口见 Objects/moduleobject.c 中的 PyModule_FromSlotsAndSpec(对 NULL slots 直接报错),以及 Include/moduleobject.h 中的声明——该段声明被 Py_LIMITED_API 门控在 3.15 版本(_Py_PACK_VERSION(3, 15))之后,与文档标注一致。
7. 传统方式:PyModuleDef 结构体
传统上,扩展模块用一个 module definition 作为“模块如何创建”的描述:与其直接使用 slots 数组,定义结构体为最常见功能提供了专用成员,并允许以 slots 作为扩展机制。这种方式仍然可用,且暂无移除计划。
7.1 PyModuleDef 结构体成员
struct PyModuleDef {
PyModuleDef_Base m_base;
const char *m_name;
const char *m_doc;
Py_ssize_t m_size;
PyMethodDef *m_methods;
PyModuleDef_Slot *m_slots;
traverseproc m_traverse;
inquiry m_clear;
freefunc m_free;
};
该结构在源码 Include/moduleobject.h 中定义。文档对结构体本身的要求:必须静态分配(或以其他方式保证在任何由它创建的模块存在期间有效);通常每种这样定义的扩展模块只有一个该类型的变量。ABI 兼容性方面:该结构体(含全部成员)在非 free-threaded 构建(abi3)的 Stable ABI 中;而在 free-threaded 构建的 Stable ABI(abi3t)中,该结构体是不透明的、实际上不可用——此时应改用 slots 方式。
各成员的语义:
| 成员 | 对应 slot | 说明 |
|---|---|---|
m_base |
— | 类型 PyModuleDef_Base,必须初始化为 PyModuleDef_HEAD_INIT。源码中(Include/moduleobject.h)可见其三个字段:m_init(仅用于支持多次初始化的遗留单阶段扩展的重初始化函数)、m_index(模块在解释器 modules_by_index 缓存中的下标,由 PyModuleDef_Init() 设置)、m_copy(遗留不可重初始化模块首次加载后 __dict__ 的副本,由 import.c 中的 fix_up_extension() 设置)。PyModuleDef_Base 同样在 abi3 Stable ABI 中、在 abi3t 中不透明 |
m_name |
Py_mod_name |
模块名称 |
m_doc |
Py_mod_doc |
模块文档字符串;设为 NULL 等价于省略该槽 |
m_size |
Py_mod_state_size |
模块状态大小(字节)。设为 0 等价于省略该槽。使用遗留单阶段初始化或经 PyModule_Create/PyModule_Create2 动态创建模块时,m_size 可设为 -1,表示模块具有全局状态、不支持子解释器 |
m_methods |
Py_mod_methods |
模块级函数表;设为 NULL 等价于省略该槽 |
m_slots |
— | 附加 slots 数组,以 {0, NULL} 条目结尾。注意条目使用的是较旧的 PyModuleDef_Slot 结构体(而非 PySlot)。若数组中出现了与 PyModuleDef 成员对应的槽,值必须完全一致——例如 m_slots 中用了 Py_mod_name,则 m_name 必须指向同一指针(而不只是等价的字符串)。3.5 之前该成员恒为 NULL,当时定义为 inquiry m_reload |
m_traverse / m_clear / m_free |
Py_mod_state_traverse / Py_mod_state_clear / Py_mod_state_free |
分别对应三个状态槽;设为 NULL 等价于省略。3.9 起,这三个函数不再在模块状态分配之前被调用 |
PyModuleDef_Slot(Include/moduleobject.h)是一个 {int slot; void *value;} 的旧式结构体,3.5 新增。文档说明了它与 PySlot 的等价转换关系:每个 PyModuleDef_Slot 条目 modslot 被解释为
(PySlot){
.sl_id = modslot.slot,
.sl_flags = PySlot_INTPTR | sub_static,
.sl_ptr = modslot.value
}
其中 sub_static 在槽要求静态标志(如 Py_mod_methods)时、或(若存在)父级 Py_mod_slots 槽带有该标志时,取 PySlot_STATIC。两个方向都可以互相嵌套:PyModuleDef_Slot 数组可经 Py_mod_slots 装入 PySlot 数组,反之 PySlot 子数组可经 Py_mod_slot_subslots(文档中写作 Py_mod_slots/PySlot 互转机制,即 Py_mod_slots 与 Py_slot_subslots 两个槽 ID)装入。
相关的类型与槽常量:
PyTypeObject PyModuleDef_Type:PyModuleDef对象的类型(源码见 Objects/moduleobject.c,tp_name为"moduledef");Py_mod_slots槽(3.15 新增):作用类似PySlot_SUBSLOTS,但声明的是PyModuleDef_Slot结构体数组。
7.2 从 PyModuleDef 创建模块的 API
| 函数 | 说明 |
|---|---|
PyObject *PyModule_Create(PyModuleDef *def) |
根据 def 创建新模块对象。这是一个宏,转调 PyModule_Create2,module_api_version 取 PYTHON_API_VERSION;使用 limited API 时取 PYTHON_ABI_VERSION |
PyObject *PyModule_Create2(PyModuleDef *def, int module_api_version) |
同上,但假定 API 版本为 module_api_version;若版本与运行解释器不符,会发出 RuntimeWarning。出错返回 NULL 并设置异常。不支持 slots:def 的 m_slots 成员必须为 NULL。文档提示:绝大多数场景应改用 PyModule_Create,确定需要时再用它 |
PyObject *PyModule_FromDefAndSpec(PyModuleDef *def, PyObject *spec) |
宏,转调 PyModule_FromDefAndSpec2,版本参数同上。3.5 新增。软弃用:新代码建议改用 PyModule_FromSlotsAndSpec |
PyObject *PyModule_FromDefAndSpec2(PyModuleDef *def, PyObject *spec, int module_api_version) |
根据 def 与 ModuleSpec spec 创建新模块,版本不匹配时发 RuntimeWarning;出错返回 NULL 并设置异常。注意它不处理执行槽(Py_mod_exec),必须同时调用 PyModule_FromDefAndSpec 与 PyModule_ExecDef 才能完成初始化。3.5 新增。软弃用:新代码建议改用 PyModule_FromSlotsAndSpec |
int PyModule_ExecDef(PyObject *module, PyModuleDef *def) |
处理 def 中声明的执行槽(Py_mod_exec)。3.5 新增。软弃用:要执行模块自身的执行槽,建议改用 PyModule_Exec,因为它同样适用于未从 PyModuleDef 创建的模块 |
版本常量:
PYTHON_API_VERSION/PYTHON_API_STRING:C API 版本,整型(1013)与字符串("1013")。仅为向后兼容而定义;当前常量在新 Python 版本中不再更新,不能用于版本判断,未来可能改变;PYTHON_ABI_VERSION/PYTHON_ABI_STRING:定义为3与"3",情况同上。
源码侧的印证:Objects/moduleobject.c 中的 check_api_version() 正是“版本不匹配则发 RuntimeWarning”的实现——它比较 module_api_version 与 PYTHON_API_VERSION/PYTHON_ABI_VERSION,不匹配时用 PyErr_WarnFormat(PyExc_RuntimeWarning, 1, ...) 发出“Python C API version mismatch for module ...”警告;而 Objects/moduleobject.c 的 PyModule_Create2 会先检查导入机制是否已初始化,未初始化时抛出 SystemError("Python import machinery not initialized"),再转调内部函数 _PyModule_CreateInitialized()(该内部函数在检测到 m_slots 非空时抛出 SystemError,对应文档“不支持 slots”的约束)。
8. 支持函数(模块初始化辅助)
以下函数用于帮助初始化模块对象,适用于模块的执行槽(Py_mod_exec)、遗留单阶段初始化的初始化函数,或动态创建模块的代码。
8.1 添加对象:AddObjectRef / Add / AddObject
三个函数语义相近,区别在于引用计数归属,这是最容易写出引用泄漏的地方:
| 函数 | 引用语义 | 版本 |
|---|---|---|
int PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value) |
把对象以 name 加入 module。成功返回 0;出错抛异常返回 -1。不偷引用:调用方保留所有权。宽容地接受“已设置异常的 NULL 值”——此时返回 -1 且不改动已抛出的异常。3.10 新增 |
|
int PyModule_Add(PyObject *module, const char *name, PyObject *value) |
类似 PyModule_AddObjectRef,但偷取 value 的引用(即使出错也偷)。可以直接把返回新引用的函数结果传进去,无需检查或暂存。3.13 新增 |
|
int PyModule_AddObject(PyObject *module, const char *name, PyObject *value) |
类似 PyModule_AddObjectRef,但仅在成功时偷取引用。文档明确建议改用 PyModule_Add 或 PyModule_AddObjectRef,因为 PyModule_AddObject 极易误用造成泄漏;3.13 起被软弃用 |
文档给出的示例代码:
/* PyModule_AddObjectRef:不偷引用,需自行释放 */
static int
add_spam(PyObject *module, int value)
{
PyObject *obj = PyLong_FromLong(value);
if (obj == NULL) {
return -1;
}
int res = PyModule_AddObjectRef(module, "spam", obj);
Py_DECREF(obj);
return res;
}
/* 等价写法:利用“接受带异常的 NULL”省去显式判空,
注意此处必须用 Py_XDECREF(),因为 obj 可能是 NULL */
static int
add_spam(PyObject *module, int value)
{
PyObject *obj = PyLong_FromLong(value);
int res = PyModule_AddObjectRef(module, "spam", obj);
Py_XDECREF(obj);
return res;
}
/* PyModule_Add:偷引用(即使出错也偷),可直接链式调用 */
if (PyModule_Add(module, "spam", PyBytes_FromString(value)) < 0) {
goto error;
}
/* PyModule_AddObject:仅成功时偷引用,失败时必须手动释放 */
PyObject *obj = PyBytes_FromString(value);
if (PyModule_AddObject(module, "spam", obj) < 0) {
/* 'obj' 非 NULL 且失败时,必须用 Py_XDECREF() 删除
'obj' 的强引用;'obj' 为 NULL 时 Py_XDECREF() 是空操作 */
Py_XDECREF(obj);
goto error;
}
/* 成功路径上 PyModule_AddObject() 已偷走 obj 的引用:
此处无需 Py_XDECREF(obj) */
补充约束:传给这些函数的不同 name 字符串数量应尽量少,通常只用静态分配的字符串作为 name;对运行时才确定的名称,建议直接调用 PyUnicode_FromString 与 PyObject_SetAttr。更多细节参见 PyUnicode_InternFromString(它可能被内部用于创建键对象)。
8.2 常量与类型
| API | 说明 |
|---|---|
int PyModule_AddIntConstant(PyObject *module, const char *name, long value) |
以 name 向 module 添加整型常量,可在模块初始化函数中使用。出错返回 -1 并设异常,成功返回 0。内部依次调用 PyLong_FromLong 与 PyModule_AddObjectRef |
int PyModule_AddStringConstant(PyObject *module, const char *name, const char *value) |
添加字符串常量,value 必须 NUL 结尾。内部调用 PyUnicode_InternFromString 与 PyModule_AddObjectRef |
PyModule_AddIntMacro(module, macro) |
宏:以 macro 的名称与值添加整型常量,例如 PyModule_AddIntMacro(module, AF_INET) 会以 AF_INET 之名、AF_INET 之值加入 module |
PyModule_AddStringMacro(module, macro) |
宏:以宏名与宏值添加字符串常量 |
int PyModule_AddType(PyObject *module, PyTypeObject *type) |
向 module 添加类型对象;内部调用 PyType_Ready 完成类型初始化;类型名取自 tp_name 最后一个 . 之后的分量。3.9 新增 |
8.3 函数表与 docstring
| 函数 | 说明 |
|---|---|
int PyModule_AddFunctions(PyObject *module, PyMethodDef *functions) |
把以 NULL 结尾的 functions 数组中的函数加入 module。由于 C 模块级函数没有共享的模块命名空间,C 实现的模块级“函数”通常把模块对象作为第一个参数接收(类似 Python 类的实例方法)。从 PyModuleDef 创建模块时(如 multi-phase-initialization、PyModule_Create、PyModule_FromDefAndSpec)本函数会被自动调用;若作者偏好把函数分散在多个 PyMethodDef 数组中定义,则应直接调用它。functions 数组必须静态分配(或保证比模块对象活得久)。3.5 新增 |
int PyModule_SetDocString(PyObject *module, const char *docstring) |
设置 module 的 docstring。从 PyModuleDef 创建模块时同样会被自动调用。成功返回 0,出错返回 -1 并设异常。3.5 新增 |
int PyUnstable_Module_SetGIL(PyObject *module, void *gil) |
用 Py_mod_gil 的取值声明 module 是否支持无 GIL 运行;在使用单阶段初始化时,必须在模块的初始化函数中调用。若初始化期间未调用,导入机制假定模块不支持无 GIL 运行。仅在启用 --disable-gil 的 Python 构建中可用。3.13 新增 |
源码印证:PyModule_AddFunctions 的底层 _add_methods_to_object()(Objects/moduleobject.c)会拒绝 METH_CLASS/METH_STATIC 标志(抛 ValueError: module functions cannot set METH_CLASS or METH_STATIC),并用 PyCFunction_NewEx(fdef, (PyObject*)module, name) 把模块对象绑定为函数的 self 参数——正对应文档“模块级函数以模块为第一参数”的描述。
9. 模块查找(单阶段初始化)
遗留的单阶段初始化方案创建的是单例模块,可在当前解释器上下文中查找:只需模块定义的引用,就能稍后取回模块对象。但注意:这些函数对多阶段初始化创建的模块无效,因为同一份定义可以创建多个此类模块。
| 函数 | 说明 |
|---|---|
PyObject *PyState_FindModule(PyModuleDef *def) |
返回为当前解释器由 def 创建的模块对象。要求此前已用 PyState_AddModule 把模块对象挂到解释器状态上;找不到或尚未挂载时返回 NULL |
int PyState_AddModule(PyObject *module, PyModuleDef *def) |
把模块对象挂到解释器状态,使其可被 PyState_FindModule 访问。仅对单阶段初始化的模块有效。Python 在导入单阶段初始化模块后会自动调用 PyState_AddModule,因此模块初始化代码中无需(但无害地)调用;只有当模块自身的初始化代码随后要调用 PyState_FindModule 时才需要显式调用。该函数主要面向实现替代导入机制的场景(直接调用,或参考其实现了解所需的状态更新细节)。若此前用同一 def 挂过模块,则被新 module 替换。调用方必须拥有已附加的线程状态(attached thread state)。出错返回 -1 并设异常,成功返回 0。3.3 新增 |
int PyState_RemoveModule(PyModuleDef *def) |
从解释器状态中移除由 def 创建的模块对象。出错返回 -1 并设异常,成功返回 0。调用方必须拥有已附加的线程状态。3.3 新增 |
10. 选型建议与版本速查
如何为扩展模块选择定义方式(综合本文各节约束):
- 需要同时兼容旧版 Python,或目标构建使用非 free-threaded Stable ABI(
abi3):使用传统的PyModuleDef(PyModule_Create/PyModule_FromDefAndSpec+PyModule_ExecDef),因为PyModuleDef与PyModuleDef_Base在该 ABI 中可用; - 目标为较新的 CPython(3.15+,尤其 free-threaded
abi3t构建,PyModuleDef在该 ABI 中不透明):使用PySlot数组(PyModule_FromSlotsAndSpec+PyModule_Exec),并记得必填Py_mod_abi槽; - 多阶段初始化场景下无论哪种方式,
From*与Exec*都必须成对调用,模块才算完整初始化。
API 版本速查(依据文档标注与源码中的 Py_LIMITED_API 门控):
| API | 新增版本 |
|---|---|
PyModule_GetFilenameObject、PyState_AddModule、PyState_RemoveModule |
3.2(PyModule_GetFilename 同期弃用) |
PyModule_NewObject、PyModule_GetNameObject |
3.3 |
Py_mod_create/Py_mod_exec、PyModule_AddFunctions、PyModule_SetDocString、PyModuleDef.m_slots、PyModuleDef_Slot、PyModule_FromDefAndSpec(2)、PyModule_ExecDef |
3.5 |
PyModule_AddObjectRef |
3.10 |
Py_mod_multiple_interpreters |
3.12 |
Py_mod_gil、PyUnstable_Module_SetGIL、PyModule_Add |
3.13 |
slots 定义方式(Py_mod_name/Py_mod_doc/Py_mod_abi/Py_mod_methods/Py_mod_state_*/Py_mod_token/Py_mod_slots)、PyModule_GetStateSize、PyModule_GetToken、PyModule_FromSlotsAndSpec、PyModule_Exec |
3.15 |
PyModule_AddObject |
软弃用(3.13 起建议改用 PyModule_Add/PyModule_AddObjectRef);PyModule_FromDefAndSpec(2)、PyModule_ExecDef 同样建议改用 slots 版本 |
参考文件(均相对仓库根目录):
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00