3

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.

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Awesome, thank you for the fast answer! Doing my first steps into C and Python C API, so bear with the question stupidity.
1

This is the end result, in case someone needs a working snippet:

static PyObject* MyLib_PyValue(PyObject *self, PyObject *args, PyObject *kwargs) {
    static char *kwlist[] = { "x", NULL };
    PyObject *pyX = NULL;
    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:value", kwlist, &pyX)) {
        return NULL;
    }
    if (pyX == NULL) { // getter
        return PyLong_FromLong(APIGetValue());
    }
    // setter
    int x = (int)PyLong_AsLong(pyX);
    if (PyErr_Occurred()) {
        return NULL;
    }
    if (x < 0) {
        PyErr_SetString(PyExc_ValueError, "x must be positive");
        return NULL;
    }
    APISetValue(x);
    Py_RETURN_NONE;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.