从具有额外 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 模块对象一起存储?
我的一些想法:
我认为最干净的选择是从 C 继承 Python 的 ModuleType
并在子类中添加一个字段来存储数据结构。 extension types tutorial 显然很清楚如何在 C 中子类化一个内置的 Python 类型,所以我希望能够做这样的事情:
typedef struct {
PyModuleObject module;
struct cheese_data* data;
} PyCheeseModule;
static PyTypeObject PyCheeseModule_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_basicsize = sizeof(PyCheeseModule),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_base = &PyModule_Type,
/* other fields */
};
但是 PyModuleObject
没有作为 Python/C API 的一部分公开,所以我不能这样使用它。
我也考虑过动态分配一个PyModuleDef
for each cheese module, and using PyModule_FromDefAndSpec()
along with PyModule_ExecDef()
来创建和执行实际的模块。但我不太确定 PyModuleDef
是否应该以这种方式使用,因为文档仅使用静态定义的 C 扩展模块对其进行了演示,而且无论如何我都必须释放 PyModuleDef
对象本身哪一种让我回到同样的问题。
另一种方法是将 C 数据结构包装在 Python 对象中,然后将其添加到模块的字典中。但是 Python 代码可能会更改或取消设置该属性。我想我可以找到解决这个问题的方法,但它看起来很不优雅。
也许我应该在这个项目中使用 Cython。但即便如此,如果这个问题在 Cython 中是可以解决的,那么使用普通的旧 Python/C API 也应该 可能 来解决这个问题,至少对于教育我想知道如何计算值。
如有必要,我可以使用一些额外的代码来扩展这些尝试。
几个选项:
使用堆类型而不是模块?
很有可能您实际上并不需要模块对象。模块对象有两个主要特征 - 它们有一个 __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__
提供 PyMemberDef
s - 例如:
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.
的完全面向未来的解决方案
我正在开发自定义 Python 加载程序,它从特定类型的非 Python 文件创建 Python 模块,我们称它为“奶酪文件”。我正在将我的项目编写为 C 扩展模块,因为这些“奶酪文件”需要由相当复杂的 C 库处理(其次,作为了解 Python/C API 的一种方式)。
在处理奶酪文件时,C 库会分配一些数据结构,这些数据结构在删除 Python 模块对象后需要重新分配。我的问题是,我如何 can/should 将这些 C 数据结构与 Python 模块对象一起存储?
我的一些想法:
我认为最干净的选择是从 C 继承 Python 的
ModuleType
并在子类中添加一个字段来存储数据结构。 extension types tutorial 显然很清楚如何在 C 中子类化一个内置的 Python 类型,所以我希望能够做这样的事情:typedef struct { PyModuleObject module; struct cheese_data* data; } PyCheeseModule; static PyTypeObject PyCheeseModule_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_basicsize = sizeof(PyCheeseModule), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_base = &PyModule_Type, /* other fields */ };
但是
PyModuleObject
没有作为 Python/C API 的一部分公开,所以我不能这样使用它。我也考虑过动态分配一个
PyModuleDef
for each cheese module, and usingPyModule_FromDefAndSpec()
along withPyModule_ExecDef()
来创建和执行实际的模块。但我不太确定PyModuleDef
是否应该以这种方式使用,因为文档仅使用静态定义的 C 扩展模块对其进行了演示,而且无论如何我都必须释放PyModuleDef
对象本身哪一种让我回到同样的问题。另一种方法是将 C 数据结构包装在 Python 对象中,然后将其添加到模块的字典中。但是 Python 代码可能会更改或取消设置该属性。我想我可以找到解决这个问题的方法,但它看起来很不优雅。
也许我应该在这个项目中使用 Cython。但即便如此,如果这个问题在 Cython 中是可以解决的,那么使用普通的旧 Python/C API 也应该 可能 来解决这个问题,至少对于教育我想知道如何计算值。
如有必要,我可以使用一些额外的代码来扩展这些尝试。
几个选项:
使用堆类型而不是模块?
很有可能您实际上并不需要模块对象。模块对象有两个主要特征 - 它们有一个 __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
.
您可以处理 Py_tp_new
槽中的初始化和 Py_tp_dealloc
槽中的清理(像往常一样)。
最后,您将为 __name__
和 __dict__
提供 PyMemberDef
s - 例如:
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.