I am passing an extension type object to a Python function, which needs to pass a variable in this type to a C function. My extension type looks like this:
typedef struct {
PyObject_HEAD
rec_rset_t rst; //need to pass this to C function
} RSet;
The rec_rset_t is a pointer to a struct, like this:
typedef struct rec_rset_s *rec_rset_t;
where rec_rset_s is defined thus:
struct rec_rset_s
{
size_t size;
int a,b;
}
So I have a Python extension function, that receives an RSet object as an argument.
static PyObject*
Recon_query(Recon *self, PyObject *args, PyObject *kwds)
{
const char *type;
RSet *tmp = PyObject_NEW(RSet, &RSetType);
rec_rset_t res;
static char *kwlist[] = {"type", "rset", NULL};
if (! PyArg_ParseTupleAndKeywords(args, kwds, "zO", kwlist,
&type, &rset))
{
return NULL;
}
res = cquery(self->rcn, type, rset->rst);
tmp->rst = res;
return Py_BuildValue("O",tmp);
}
The problem is that I want to be able to pass None
for the RSet
object, and have it translate to NULL in C as well as have the variable rst
be NULL. If I pass None
, I get a segmentation fault, because the "O"
option of PyArg_ParseTupleAndKeywords
does not handle None value of PyObject
(this is unlike the "s"
option, where we can use "z"
if we are passing a NULL
string). I tried manually checking for Py_None
object, but it didn't work. So at present I'm doing something not very elegant, like this:
if(rset->rst == 0x89e8a0)
rset->rst = NULL;
because that was the value of rset->rst
when I passed None
, and then passing this rset->rst
to the cquery
function.
How does one pass None
values to PyArg_ParseTuple
when receiving an extension type object? Is there a general way in which this is done?
Edit:
I had wrongly checked the value of rset->rst for Py_None. Checking
if((PyObject *)rset == Py_None)
evaluates to true, so yes, None is handled. But the value rset->rst (which I pass to cquery) is not NULL, which is what I want. Is manually setting rset->rst = NULL the only way to do this?
The problem is that if rset is set to Py_None (a PyObject*), only the PyObject_HEAD-defined fields are guaranteed to be valid; essentially you've gotten back a pointer to something that isn't an RSet.
Instead of setting rset->rst to NULL (which, if rset == Py_None, could be modifying memory that it shouldn't be), you should modify your code so that it initially refers to a PyObject* and then only casts to an RSet* once it is certain it is not Py_None:
You might also want to explicitly check the object type before casting to RSet. (Or use O!, which does type validation for you, but I seem to recall that doesn't allow for None, which you want in this case.)