我的 PyCFunction 第一次工作,但在连续迭代后导致段错误

My PyCFunction works the first time but leads to a segfault after successive iterations

我想用 c 编写一个 python 对象,它包含一个 numpy 向量(实际上是两个,但在这个最小的例子中只有一个)。

首先,我只想创建一个带有 numpy 数组的对象,然后看看我可以在循环中将一个添加到所有数组元素。但即使这样也会导致奇怪的(可重现的)行为和段错误。

这是发生了什么(REPL):

from rletest import Rle
r = Rle(range(1, 10))
r.runs
# array([1., 2., 3., 4., 5., 6., 7., 8., 9.])
r.add()
r.runs
# array([ 2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.])
r.add()
fish: 'python' terminated by signal SIGSEGV (Address boundary error)

我可以这样解决这个问题:

... r2 = r.add()
>>> r3 = r2.add()
>>> r4 = r3.add()
>>> r4.runs
array([ 4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12.])

我怀疑这与引用计数有关?我对此完全是个新手,一无所知。任何帮助表示赞赏。这个功能(或我对它的使用)可能是违规者:

static PyObject * Rle_add(Rle* self)
{
  int N = (int)PyArray_DIM(self->runs, 0);
  double *x = (double*)PyArray_DATA(self->runs);
  add(x, N);
  return (PyObject *)self;
}

它就地更改数组以提高速度,但这不会导致错误,正如您从上面的解决方法中看到的那样。

下面是我的代码,所以这是可重现的。

我有以下文件:

test_rle.c:

#include <Python.h>
#include <numpy/arrayobject.h>
#include "structmember.h"
#include "add.h"

/* static PyObject *add(PyObject *self, PyObject *args); */

typedef struct {
    PyObject_HEAD
    PyObject *runs; /* run lengths */
} Rle;


static void
Rle_dealloc(Rle* self)
{
    Py_XDECREF(self->runs);
    Py_TYPE(self)->tp_free((PyObject*)self);
}


static PyObject *
Rle_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{

  printf("New was called! New was called! New was called!\n");
    Rle *self;
    PyObject *rs, *vs;

    import_array();

    if (!PyArg_ParseTuple(args, "O", &rs))
      return NULL;

    PyObject *runs = PyArray_FROM_OTF(rs, NPY_DOUBLE, NPY_IN_ARRAY);

    self = (Rle *)type->tp_alloc(type, 0);
    if (self != NULL) {

        self->runs = runs;
        if (self->runs == NULL) {
            Py_DECREF(self);
            return NULL;
        }
    }

    printf("Reference count after new: %d\n", Py_REFCNT(self));
    return (PyObject *)self;
}

static int
Rle_init(Rle *self, PyObject *args, PyObject *kwds)
{
  printf("Init was called! Init was called! Init was called!\n");

  printf("Reference count after init: %d\n", Py_REFCNT(self));
    return 0;
}


static PyMemberDef Rle_members[] = {
    {"runs", T_OBJECT_EX, offsetof(Rle, runs), 0,
     "Run lengths"},
    {NULL}  /* Sentinel */
};

static PyObject * Rle_add(Rle* self)
{
  int N = (int)PyArray_DIM(self->runs, 0);
  double *x = (double*)PyArray_DATA(self->runs);
  add(x, N);
  return (PyObject *)self;
}


static PyMethodDef Rle_methods[] = {
    {"add", (PyCFunction)Rle_add, METH_NOARGS,
     "Add 1 to the runs"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject RleType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "rle.Rle",             /* tp_name */
    sizeof(Rle),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Rle_dealloc, /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Rle objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Rle_methods,             /* tp_methods */
    Rle_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Rle_init,      /* tp_init */
    0,                         /* tp_alloc */
    Rle_new,                 /* tp_new */
};

static PyModuleDef rletestmodule = {
    PyModuleDef_HEAD_INIT,
    "rletest",
    "Example module that creates an extension type.",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_rletest(void)
{
    PyObject* m;

    if (PyType_Ready(&RleType) < 0)
        return NULL;

    m = PyModule_Create(&rletestmodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&RleType);
    PyModule_AddObject(m, "Rle", (PyObject *)&RleType);
    return m;
}

setup.py:

from distutils.core import setup, Extension
setup(name="rle", version="1.0",
      ext_modules=[Extension("rletest", ["test_rle.c", "add.c"])])

add.h

void add(double *x, int N);

add.c:

#include <stdio.h>

void add(double *x, int N) {
  int n;

  for (n = 0; n < N; n++) {
    x[n] += 1.0;
  }
}

将它们全部放在同一个文件夹中 运行

python setup.py build_ext --inplace

建造它。

return (PyObject *)self;

您现在有了对 self 的额外引用,因此您需要 incref 它。因为你没有分配 return 值所以 Python 立即 decrefs 它和 r 不复存在。

(您的解决方法避免了直接 decref 但我很惊讶它确实会在稍后导致分段错误。)