I want to implement a following Python function in a C extension module:
def value(x: Optional[int] = None) -> Optional[int]:
if x is None:
# act like a getter
return APIGetValue() # retrieve the value from an external library
# act like a setter
APISetValue(x) # pass the value to an external library
return None
Here is what I got so far:
static PyObject* MyLib_PyValue(PyObject *self, PyObject *args, PyObject *kwargs) {
static char *kwlist[] = { "x", NULL };
int x;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:value", kwlist, &x)) {
return NULL;
}
if (x == NULL) {
return PyLong_FromLong(APIGetValue());
}
APISetValue(x);
Py_RETURN_NONE;
}
Calling the function with args works, but when calling value()
as a getter, I get 1
instead of NULL
. How should I proceed? I'm not very familiar with Python's C API yet.
First, you can't have a NULL int. NULL is a thing for pointers. Due to the way C type conversion works and how the NULL
macro is defined, x == NULL
with an int x
usually does one of two things: it behaves as x == 0
, or it produces a compile-time error.
Second, quoting the Python C API argument parsing docs,
The C variables corresponding to optional arguments should be initialized to their default value — when an optional argument is not specified, PyArg_ParseTuple() does not touch the contents of the corresponding C variable(s).
This is also true of PyArg_ParseTupleAndKeywords
. Your x
is uninitialized, and PyArg_ParseTupleAndKeywords
doesn't write a value for it, so accessing the value of x
in the x == NULL
comparison is undefined behavior.
You need to initialize x
, and you need to use a type that actually allows you to detect missing values. That probably means declaring x
as PyObject *x = NULL;
and passing "|O:value"
to PyArg_ParseTupleAndKeywords
, then handling the conversion to C long inside your function instead of relying on PyArg_ParseTupleAndKeywords
to do it.