在Python扩展中创建一个模块子类

4
我正在尝试创建一个Python扩展模块,并实现多阶段初始化。参考之前的问题,我得到了一些建议。根据PEP 489,推荐Py_mod_create函数返回一个模块子类,它可能是PyModule的子类,但我无法找到如何实现这一点。在所有尝试中,当我导入模块时,它会崩溃。如果Py_mod_create返回其他对象(不是PyModule子类的对象),则可以正常工作,但我不确定这是否会在未来引起问题,因为这种情况下isinstance(mymodule,types.ModuleType)返回false。

按照内置类型子类化文档的说明,我将tp_base设置为PyModule_Type,并且我的tp_init函数调用PyModule_Type.tp_init。文档还建议在结构体开始处包含超类结构体,在这种情况下是PyModuleObject。这个结构体在公共Python头文件中没有定义(它在Python源代码的moduleobject.c中定义),所以现在我将PyModuleObject字段的定义复制并粘贴到我的结构体开头。完整的代码如下:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdio.h>

struct testmod_s {
  // Fields copied from PyModuleObject in moduleobject.c
  PyObject_HEAD
  PyObject *md_dict;
  struct PyModuleDef *md_def;
  void *md_state;
  PyObject *md_weaklist;
  PyObject *md_name;
};

static int testmod_init(PyObject *self, PyObject *args, PyObject *kwds);
static PyObject *testmod_create(PyObject *spec, PyModuleDef *def);

static PyModuleDef_Slot testmod_slots[] = {
  {Py_mod_create, testmod_create},
  {0, 0}  /* Sentinel */
};

static struct PyModuleDef testmod_def = {
  PyModuleDef_HEAD_INIT,    /* m_base */
  "testmod",                /* m_name */
  NULL,                     /* m_doc */
  sizeof(struct testmod_s), /* m_size */
  NULL,                     /* m_methods */
  testmod_slots,            /* m_slots */
  NULL,                     /* m_traverse */
  NULL,                     /* m_clear */
  NULL                      /* m_free */
};

static PyTypeObject testmodtype = {
  PyVarObject_HEAD_INIT (NULL, 0)
  "testmodtype",                /* tp_name */
  sizeof (struct testmod_s),    /* tp_basicsize */
  /* fields omitted for brevity, all set to zero */
  Py_TPFLAGS_DEFAULT |
  Py_TPFLAGS_BASETYPE,          /* tp_flags */
  /* fields omitted for brevity, all set to zero */
  testmod_init,                 /* tp_init */
  0,                            /* tp_alloc */
  0,                            /* tp_new */
};

PyMODINIT_FUNC
PyInit_testmod(void)
{
  testmodtype.tp_base = &PyModule_Type;
  if (PyType_Ready(&testmodtype)) {
    return NULL;
  }
  PyObject *moduledef = PyModuleDef_Init(&testmod_def);
  if (moduledef == NULL) {
    return NULL;
  }
  return moduledef;
}

static int testmod_init(PyObject *self, PyObject *args, PyObject *kwds)
{
  if (PyModule_Type.tp_init((PyObject *)self, args, kwds) < 0) {
    return -1;
  }
  return 0;
}

static PyObject *testmod_create(PyObject *spec, PyModuleDef *def)
{
  struct testmod_s *module = PyObject_New(struct testmod_s, &testmodtype);
  if (module == NULL) {
    return NULL;
  }
  return (PyObject *) module;
}

导入这个模块会导致segfault。我做错了什么?

我在macOS 12.0.1上使用Anaconda构建的Python 3.8.5运行:

>>> sys.version
'3.8.5 (default, Sep  4 2020, 02:22:02) \n[Clang 10.0.0 ]'
1个回答

2

经过一些测试,我可以通过从moduleobject.c中复制代码部分来构建自定义模块类型。

你的问题是你的代码创建了一个module子类的实例,但从未初始化它,并在关键成员中获得随机值。此外,模块应该是可GC收集的,因此你必须使用PyObject_GC_New创建自定义模块。

以下代码用完整的模块初始化替换了你最初的testmod_create函数:

...
// copied from moduleobject.c
static int
module_init_dict(struct testmod_s* mod, PyObject* md_dict,
    PyObject* name, PyObject* doc)
{
    _Py_IDENTIFIER(__name__);
    _Py_IDENTIFIER(__doc__);
    _Py_IDENTIFIER(__package__);
    _Py_IDENTIFIER(__loader__);
    _Py_IDENTIFIER(__spec__);

    if (md_dict == NULL)
        return -1;
    if (doc == NULL)
        doc = Py_None;

    if (_PyDict_SetItemId(md_dict, &PyId___name__, name) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___doc__, doc) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___package__, Py_None) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___loader__, Py_None) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___spec__, Py_None) != 0)
        return -1;
    if (PyUnicode_CheckExact(name)) {
        Py_INCREF(name);
        Py_XSETREF(mod->md_name, name);
    }

    return 0;
}

static PyObject* testmod_create(PyObject* spec, PyModuleDef* def)
{
    struct testmod_s* module = PyObject_GC_New(struct testmod_s, &testmodtype);
    if (module == NULL) {
        return NULL;
    }
    PyObject* name = PyUnicode_FromString("testmod");
    if (name == NULL) {
        Py_DECREF(module);
        return 0;
    }
    module->md_def = NULL;
    module->md_state = NULL;
    module->md_weaklist = NULL;
    module->md_name = NULL;
    module->md_dict = PyDict_New();
    int cr = module_init_dict(module, module->md_dict, name, NULL);
    Py_DECREF(name);
    if (cr != 0) {
        Py_DECREF(module);
        return NULL;
    }

    return (PyObject*)module;
}

谢谢这个。我很难相信在PEP 489中,“模块子类”这个术语的意思就是这样。我想知道是否还有其他方法。 - chris
1
在我的测试中,我已经向模块添加了一个整数成员,为 x 变量添加了 getter/setter,并且确实可以将其用作 模块属性import testmod print(testmod.x) 实际上调用了 getter,而 testmod.x = 5 调用了 setter。 - Serge Ballesta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接