【发布时间】: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