PintoDoido
PintoDoido

Reputation: 1051

Python C++ API - how to access/set class atributes from Python?

I have created getsetters for a public variable number_bananas I have in my Box class. number_bananas is public because the box is unlocked, anyone can eat bananas or put more in the box.

Here is my PyBox type:

typedef struct 
{
    PyObject_HEAD
    Box *bx;
} PyBox;

In my Box class I have defined:

class Box {
   public:
      Box(double l, double b, double h);
      int number_bananas;
   ...

Box::Box(double l, double b, double h)
{
        number_bananas = 11;
        length         = l;
        breadth        = b;
        height         = h;       
}

and here are the get/setters I have defined:

static PyObject *pyBox_getBananas(PyBox *self, void *closure)
{
    Py_INCREF(self->bx->number_bananas);
    return self->bx->number_bananas;
}

static int pyBox_setBananas(PyBox *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "You're trying to put something that is not a banana!!");
        return -1;
    }

    Py_DECREF(self->bx->number_bananas);
    Py_INCREF(value);
    self->bx->number_bananas = value;

    return 0;
}

static PyGetSetDef pyBox_getseters[] = {
    {"number_bananas", (getter)pyBox_getBananas, (setter)pyBox_setBananas, "number of bananas", NULL},
    {NULL}
};

The constructor I have used to instantiate the Box class is defined by:

static int pyBox_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char* nams[] = {"length","breadth","height", NULL};
    int l, b, h;
    if(!PyArg_ParseTupleAndKeywords(args, kwds, "iii", nams, &l, &b, &h))
        return -1;

    ((PyBox *)self)->bx = &(Box::Box(l,b,h));

    return 0;
}

static PyObject *pyBox_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyBox *self;
    self = (PyBox *) type->tp_alloc(type, 0);

    return (PyObject *)self;
}

The module compiles successfully. However, I cannot find how to access the property number_bananas from Python.

The following results in Fatal error (segfault):

import adventureIsland

bo = adventureIsland.box(1,1,1)

print(bo.number_bananas)

So... my question is how do I access/set the number_bananas from Python?

Thanks!

Upvotes: 1

Views: 92

Answers (1)

DavidW
DavidW

Reputation: 30907

self->bx->number_bananas is not a PyObject* so can't be increfed or decrefed. Instead you want to be converting it to/from a PyObject*. In Python3 you do this with various PyLong_* functions.

Some untested code would be:

static PyObject *pyBox_getBananas(PyBox *self, void *closure)
{
    return PyLong_FromLong(self->bx->number_bananas);
}

static int pyBox_setBananas(PyBox *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "You're trying delete the attribute!!");
        return -1;
    }

    int valuei = PyLong_AsLong(value);
    if (valuei==-1 and PyErr_Occurrred()) {
        return -1;
    }

    self->bx->number_bananas = valuei;

    return 0;
}

Potentially there might be problems with too large numbers if int (number_of_bananas) and long (the result of PyLong_AsLong) aren't the same size, which it might be worth you trying to identify and raise an exception.


You were having further issues due to a constructor problem:

((PyBox *)self)->bx = &(Box::Box(l,b,h));

This sets bx to point to a temporary Box that stops existing almost as soon as it is created. What you should be doing is allocating a Box on the heap using the new operator. (What's happening now is that the memory you're pointing to is being reused in other functions so the "value" is changing in confusing ways).

((PyBox *)self)->bx = new Box::Box(l,b,h);

You should then make sure you delete this in the destructor (to avoid a memory leak).

Upvotes: 1

Related Questions