Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 3121 – 扩展模块初始化和终结

作者:
Martin von Löwis <martin at v.loewis.de>
状态:
最终版
类型:
标准跟踪
创建日期:
2007年4月27日
Python 版本:
3.0
发布历史:


目录

重要

本 PEP 是一份历史文档。最新的规范文档现在可以在 PyInit_modulename()PyModuleDef 找到。

×

有关如何提出更改,请参阅 PEP 1

摘要

扩展模块的初始化目前存在一些缺陷。模块没有清理机制,入口点名称可能导致命名冲突,入口函数不遵循常规的调用约定,并且对多解释器的支持不佳。本 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

最后修改:2025-02-01 08:59:27 GMT