【问题标题】:How to instantiate a custom object from within a C module?如何从 C 模块中实例化自定义对象?
【发布时间】:2020-04-04 07:59:56
【问题描述】:

我在创建从 C 模块中编写的类(类型)的实例时遇到问题。我写了一个最小的、独立的例子来说明这一点。只需将 spam.c、spamtest.py 和 setup.py 这三个文件复制粘贴到一个目录并运行

$ python setup.py develop && python spamtest.py

我不明白为什么在实例化垃圾邮件实例时不调用基本函数 new() 和 init()。不用说,这会在实际应用程序中导致大量时间段错误,其中这些函数为新创建的对象分配动态内存。

这是在 Python 的调试版本下运行 spamtest.py 时发生的情况。注意 new() 和 init() 在从 Python 解释器中实例化垃圾邮件对象时被调用,而不是从 C 中。

(pyenv36d) $ python spamtest.py 
---------
From Python
New Spam at 0x7fa7a5ca9280
Init Spam at 0x7fa7a5ca9280
Finalize Spam at 0x7fa7a5ca9280
---------
From C
Finalize Spam at 0x7fa7a5ca92c0
---------
* ob
object  : <refcnt 0 at 0x7fa7a5bb6670>
type    : bytes
refcount: 0
address : 0x7fa7a5bb6670
* op->_ob_prev->_ob_next
<NULL object>
* op->_ob_next->_ob_prev
object  : <refcnt 0 at 0x7fa7a5bb6670>
type    : bytes
refcount: 0
address : 0x7fa7a5bb6670
Fatal Python error: UNREF invalid object

Current thread 0x00007fa7a5ff8080 (most recent call first):
Aborted
(pyenv36d) $ 

模块:

#include <Python.h>

typedef struct {
     PyObject_HEAD
} Spam;

static PyObject *new(PyTypeObject *type,
         PyObject *args, PyObject *kw) {
     Spam *self;

     self = (Spam *) type->tp_alloc(type, 0);
     fprintf(stderr, "New Spam at %p\n", self);
     return (PyObject*)self;
}

static int init(Spam *self, PyObject *args, PyObject *kw) {
     fprintf(stderr, "Init Spam at %p\n", self);
     return 0;
}

static void finalize(PyObject *self) {
     fprintf(stderr, "Finalize Spam at %p\n", self);
}

static PyMethodDef spam_methods[] = {
     {NULL, NULL, 0, NULL},
};

static PyTypeObject spam_type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     .tp_name = "Spam",
     .tp_basicsize = sizeof(Spam),
     .tp_flags = 0
         | Py_TPFLAGS_DEFAULT
         | Py_TPFLAGS_BASETYPE,
     .tp_doc = "Spam object",
     .tp_methods = spam_methods,
     .tp_new = new,
     .tp_init = (initproc) init,
     .tp_dealloc = finalize,
};

/* To create a new Spam object directly from C */
PyObject *make_spam() {
     Spam *spam;
     if (PyType_Ready(&spam_type) != 0) {
         Py_RETURN_NONE;
     }
     spam = PyObject_New(Spam, &spam_type);
     PyObject_Init((PyObject *)spam, &spam_type);
     return (PyObject *) spam;
}

static PyMethodDef module_methods[] = {
     {"make_spam",  (PyCFunction)make_spam, METH_NOARGS,
      "Instantiate and return a new Spam object."},
     {NULL, NULL, 0, NULL}
};

static PyModuleDef spam_module = {
     PyModuleDef_HEAD_INIT,
     "spam",
     "Defines the Spam (time, value) class"
     ,
     -1,
     module_methods
};

PyMODINIT_FUNC PyInit_spam(void) {
     PyObject *m;
     m = PyModule_Create(&spam_module);
     if (PyType_Ready(&spam_type) < 0) {
         return NULL;
     }
     PyModule_AddObject(m, "Spam", (PyObject*)&spam_type);
     return m;
}

setup.py

from setuptools import setup, Extension

spam = Extension('spam', sources=['spam.c'])

setup (
    name = 'spam',
    version = '0.1',
    description = 'Trying to instantiate an object from C',
    ext_modules = [spam],
    packages = [],
)

spamtest.py:

from spam import Spam, make_spam

print("---------\nFrom Python")
s1 = Spam()
del s1

print("---------\nFrom C")
s2 = make_spam()
del s2
print("---------")

【问题讨论】:

  • 相对于docs.python.org/3/extending/newtypes_tutorial.html,在将类型添加到模块之前,该类型缺少一个 INCREF。我不明白这会如何导致您的问题,但值得纠正。
  • 仔细观察可能造成的后果,我怀疑该类型的引用计数将在某个地方归零,并且不应该的事情正在被清除。尝试更改测试用例的顺序,或调用 Python API 两次,以查看具体是 C 代码损坏,还是第二次和后续实例化尝试。
  • 鉴于我现在怀疑这是问题所在,我重新发布了评论作为可能的答案。

标签: python python-3.x python-extensions


【解决方案1】:

一种可能性是这条线:

 PyModule_AddObject(m, "Spam", (PyObject*)&spam_type);

应该是这个(根据documentation):

Py_INCREF(&spam_type);
if (PyModule_AddObject(m, "Spam", (PyObject *) &spam_type) < 0) {
    Py_DECREF(&spam_type);
    Py_DECREF(m);
    return NULL;
}

【讨论】:

  • 感谢您的回答。与此同时,我一直在做其他事情,但是当我回到这个问题时,我会试一试。
【解决方案2】:

一个答案是通过调用类型对象来构造你的新对象,就像你从 Python 本身一样。

也就是说,将make_spam() 更改为更像:

/* To create a new Spam object directly from C */
PyObject *make_spam() {
     Spam *spam;
     if (PyType_Ready(&spam_type) != 0) {
         Py_RETURN_NONE;
     }
     spam = (Spam *)PyObject_CallObject((PyObject *)&spam_type, Py_BuildValue(""));
     /* your other init here, assuming you have some */
     return (PyObject *) spam;
}

这也让我感到困惑。我想知道为什么您的原始代码不起作用,以及是否有更好的方法可以“正确”执行此操作,但这种方法对我有用。

【讨论】:

    猜你喜欢
    • 2013-04-02
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-22
    • 1970-01-01
    相关资源
    最近更新 更多