Adding new python type : TypeError: can't set attributes of built-in/extension type

4.7k views Asked by At

Below python-c code which compiles properly

#include <Python.h>
#include <structmember.h>

struct rangerr {
        long    min;
        long    max;
};

//Python type to represent rangerr
struct py_rangerr {
    PyObject_HEAD
    struct rangerr range;
};
// * get & set methods for py_rangerr

static PyObject * py_rangerr_min_get(struct py_rangerr *self) {
    self->range.min  = 1;
        return PyLong_FromLong(self->range.min);
}
static PyObject * py_rangerr_min_set(struct py_rangerr *self) {
printf("Setter called");
self->range.min  = 1;
}


static PyObject * py_rangerr_max_get(struct py_rangerr *self) {
    self->range.max  = 10;
        return PyLong_FromLong(self->range.max);
}


//* GetSet method definition for py_rangerr
static PyGetSetDef py_rangerr_getset[] = {
    {"min",(getter)py_rangerr_min_get, (setter)py_rangerr_min_set, "min",NULL},
    {"max",(getter)py_rangerr_max_get, NULL, "max",NULL},

    /* Sentinel */
    {NULL},
};

/******************************************************************************
 */
static void py_rangerr_dealloc(struct py_rangerr *self) { 
        self->ob_type->tp_free((PyObject *)self);               
}

static PyTypeObject py_rangerr_type = {  
    PyObject_HEAD_INIT(NULL)    
     .tp_name       = "rangerr",        
    .tp_basicsize = sizeof(struct py_rangerr),
    .tp_dealloc   = (destructor) py_rangerr_dealloc,  
    .tp_flags     = Py_TPFLAGS_DEFAULT,                        
    .tp_alloc     = PyType_GenericAlloc,                       
    .tp_doc       = "rangerr",                                 
    .tp_getset    = py_rangerr_getset,                
};

void
initrangerr(void)
{
   PyObject* mod;

 mod = Py_InitModule3("rangerr", NULL, "An extension with a type.");
   if (mod == NULL) {
      return;
   }
  py_rangerr_type.tp_new = PyType_GenericNew;
  if (PyType_Ready(&py_rangerr_type) < 0){
      return;
   }


 Py_INCREF(&py_rangerr_type);
   PyModule_AddObject(mod, "rangerr", (PyObject*)&py_rangerr_type);

}

but when I try to invoke set/get method It throws below errors:

>>> import rangerr as r
>>> r.rangerr.min
<attribute 'min' of 'rangerr' objects>
>>> r.rangerr.min=2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'rangerr'
>>> type(r.rangerr.min)
<type 'getset_descriptor'>

Do I need add something like "PyMemberDef" ? Thanks for any pointers or help.

1

There are 1 answers

2
tynn On BEST ANSWER

It's not directly possible to have a default value for arguments. You could add a function to your module to set global default values as static variables.

If you only want to initialize your instance, you should define PyTypeObject.tp_init like

static int py_rangerr_init(struct py_rangerr *self, PyObject *args, PyObject *kwds) {
    static char *kwlist[] = {"min", "max",  NULL};
    int min = 0, max = 1;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ii", kwlist, &min, &max))
        return -1;

    self->range.min = min;
    self->range.max = max;

    return 0;
}

static PyTypeObject py_rangerr_type = {  
    PyObject_HEAD_INIT(NULL)
    [...]
    .tp_init      = (initproc) py_rangerr_init,
};

If you don't give values for min or max, the default values from the line int min = 0, max = 1; will be used.

In relation to your question about the usage of PyMemberDef, it is, that for types like int it might be and less repetitive less error-prone to use PyTypeObject.tp_members instead of PyTypeObject.tp_getset.

Besides the typedef of setter is:

typedef int (*setter)(PyObject *, PyObject *, void *);