PEP 3121 – 扩展模块初始化和终结
- 作者:
- Martin von Löwis <martin at v.loewis.de>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2007年4月27日
- Python 版本:
- 3.0
- 发布历史:
摘要
扩展模块的初始化目前存在一些缺陷。模块没有清理机制,入口点名称可能导致命名冲突,入口函数不遵循常规的调用约定,并且对多解释器的支持不佳。本 PEP 旨在解决这些问题。
问题
模块终结
目前,扩展模块通常只初始化一次,然后“永远”存在。唯一的例外是当调用 Py_Finalize() 时:此时初始化例程会第二次被调用。从资源管理的角度来看,这是不好的:每次调用初始化时,内存和其他资源都可能被分配,但却没有办法回收它们。结果是,目前无法完全释放 Python 分配的所有资源。
入口点名称冲突
入口点目前名为 init<module>。这可能与也名为 init<something> 的其他符号冲突。特别是,initsocket 过去已知曾发生冲突(这个具体问题通过将模块重命名为 _socket 而作为副作用得到解决)。
入口点签名
入口点目前是一个过程(返回 void)。这偏离了常规的调用约定;调用者只能通过检查 PyErr_Occurred 来判断初始化过程中是否发生错误。入口点应返回一个 PyObject*,它将是创建的模块,或者在发生异常时返回 NULL。
多解释器
目前,扩展模块在所有解释器之间共享其状态。这导致解释器之间不必要的信息泄露:一个脚本可能会永久损坏扩展模块中的对象,从而可能破坏其他解释器中的所有脚本。
规范
模块初始化例程将签名更改为
PyObject *PyInit_<modulename>()
初始化例程将在导入模块时,为每个解释器调用一次。它每次都应该返回一个新的模块对象。
为了在 C 变量中存储每个模块的状态,每个模块对象将包含一块只由模块解释的内存块。模块使用的内存量在创建模块时指定。
除了初始化函数之外,模块还可以实现一些额外的回调函数,这些函数在模块的 tp_traverse、tp_clear 和 tp_free 函数被调用时以及模块重新加载时被调用。
整个模块定义组合在一个 struct PyModuleDef 中
struct PyModuleDef{
PyModuleDef_Base m_base; /* To be filled out by the interpreter */
Py_ssize_t m_size; /* Size of per-module data */
PyMethodDef *m_methods;
inquiry m_reload;
traverseproc m_traverse;
inquiry m_clear;
freefunc m_free;
};
模块的创建已更改为接受一个可选的 PyModuleDef*。模块状态将进行空初始化。
每个模块方法都将模块对象作为第一个参数传递。要访问模块数据,将提供一个函数
void* PyModule_GetState(PyObject*);
此外,为了比通过 sys.modules 更有效地查找模块,将提供一个函数
PyObject* PyState_FindModule(struct PyModuleDef*);
这个查找函数将使用 m_base 字段中的索引,通过索引而非名称来查找模块。
由于所有 Python 对象都应通过 Python 内存管理进行控制,因此不鼓励使用“静态”类型对象,除非类型对象本身没有内存管理的状态。为了简化堆类型的定义,新增了一个方法
PyTypeObject* PyType_Copy(PyTypeObject*);
已添加。
示例
xxmodule.c 将被修改以移除 initxx 函数,并添加以下代码
struct xxstate{
PyObject *ErrorObject;
PyObject *Xxo_Type;
};
#define xxstate(o) ((struct xxstate*)PyModule_GetState(o))
static int xx_traverse(PyObject *m, visitproc v,
void *arg)
{
Py_VISIT(xxstate(m)->ErrorObject);
Py_VISIT(xxstate(m)->Xxo_Type);
return 0;
}
static int xx_clear(PyObject *m)
{
Py_CLEAR(xxstate(m)->ErrorObject);
Py_CLEAR(xxstate(m)->Xxo_Type);
return 0;
}
static struct PyModuleDef xxmodule = {
{}, /* m_base */
sizeof(struct xxstate),
&xx_methods,
0, /* m_reload */
xx_traverse,
xx_clear,
0, /* m_free - not needed, since all is done in m_clear */
}
PyObject*
PyInit_xx()
{
PyObject *res = PyModule_New("xx", &xxmodule);
if (!res) return NULL;
xxstate(res)->ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
if (!xxstate(res)->ErrorObject) {
Py_DECREF(res);
return NULL;
}
xxstate(res)->XxoType = PyType_Copy(&Xxo_Type);
if (!xxstate(res)->Xxo_Type) {
Py_DECREF(res);
return NULL;
}
return res;
}
讨论
Tim Peters 在 [1] 中报告说 PythonLabs 曾考虑过这样的功能,并列出了此 PEP 目前不支持的以下额外钩子
- 当模块对象从 sys.modules 中删除时
- 当 Py_Finalize 被调用时
- 当 Python 退出时
- 当 Python DLL 被卸载时(仅限 Windows)
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3121.rst