从具有额外 C 数据结构的扩展中创建 Python 个模块

Creating Python modules from an extension with extra C data structures

我正在开发自定义 Python 加载程序,它从特定类型的非 Python 文件创建 Python 模块,我们称它为“奶酪文件”。我正在将我的项目编写为 C 扩展模块,因为这些“奶酪文件”需要由相当复杂的 C 库处理(其次,作为了解 Python/C API 的一种方式)。

在处理奶酪文件时,C 库会分配一些数据结构,这些数据结构在删除 Python 模块对象后需要重新分配。我的问题是,我如何 can/should 将这些 C 数据结构与 Python 模块对象一起存储?

我的一些想法:

如有必要,我可以使用一些额外的代码来扩展这些尝试。

几个选项:

使用堆类型而不是模块?

很有可能您实际上并不需要模块对象。模块对象有两个主要特征 - 它们有一个 __name__ 和一个可修改的名称 space(即 __dict__),但这些可以由任何扩展类型实现。

在 Python 中您通常期望 return 模块的大多数地方实际上并不需要:您可以 return 在模块初始化期间的任何类型进程,你可以添加任何类型到 sys.modules.

模块的通用堆类型的主要优点(对您而言)是堆类型不需要 PyModuleDef 存在于它们的生命周期 - 它们创建时使用的规范被复制跨越 PyType_FromSpec(和相关函数),因此只需要为该函数调用而存在。

一个选择是创建一个通用结构来处理类型的基础:

typedef struct {
    PyObject_HEAD;
    const char* name;
    PyObject* dict;
    struct cheese_data* data;
} BaseStruct;

另一种变体是使用灵活的数组成员作为最后一个成员 (char extra_space[];),并且在提供 PyType_Spec.basicsize.

时只请求额外的 space

您可以处理 Py_tp_new 槽中的初始化和 Py_tp_dealloc 槽中的清理(像往常一样)。

最后,您将为 __name____dict__ 提供 PyMemberDefs - 例如:

static PyMemberDef cheese_members[] = {
    {"__dictoffset__", T_PYSSIZET, offsetof(BaseStruct, dict), READONLY},
    {"__name__", T_STRING, offsetof(BaseStruct, name), READONLY},
    {NULL}  /* Sentinel */
};

这应该具有模块的大部分行为,但限制很少。

使用模块

模块状态专为存储此类额外信息而设计。PEP-3121 给出了设置模块状态的示例,我将在此处尝试对其进行总结。我认为您不能直接使用该示例,因为我认为自 PEP 以来,字段的顺序发生了轻微的变化!

您创建一个用于模块状态的结构

typedef struct {
    struct cheese_data* data;
    /* alternatively cheese_data might just be part of the struct
     * rather than dynamically allocated in addition to the struct
     */
} MyModState;

PyModuleDef 中指定 m_size 选项,通常为 sizeof(MyModState)。这里的复杂之处在于您不能使用静态 PyModuleDef 因为您的目标是动态创建此模块:

PyModuleDef *mod_def = malloc(sizeof(PyModuleDef));
struct PyModuleDef mod_def_tmp = {
    PyModuleDef_HEAD_INIT,
    .m_name = "some name",
    .m_size = sizeof(MyModState),
    /* etc */
};
*mod_def = mod_def_tmp;

初始化状态结构进入 Py_mod_exec 插槽(假设您使用的是多阶段初始化):

static int
my_module_exec(PyObject *m) {
    MyModState* state = (MyModState*)PyModule_GetState(m);
    state->data = malloc(sizeof(struct cheese_data));
    /* etc */
}

虽然通过在模块规范中指定 m_free function 来处理状态清理,

static void free_module(PyObject* m) {
    MyModState* state = (MyModState*)PyModule_GetState(m);
    free(state->data);
}

/* in the PyModuleDef */
    .m_free = free_module,
/* ... */

最后一个问题是取消分配 PyModuleDef 以使其不会泄漏内存。 Looking into the Python source code 你看到 PyModuleDef 对象的最后一次使用是查找 m_free 并调用它。

因此,我认为在 m_free 中通过 free(m->m_def) 释放模块 def 应该是安全的(并且释放动态分配的任何部分,例如名称字符串).但是 - 这似乎是一个 hacky 解决方案,所以我不会将它视为所有版本的 Python.

的完全面向未来的解决方案