In the Python C API, if the second argument is not set how can I default to the value of the first?

Question:

I have an x, y parameters. Using the python C API, how can I default y to the value of x if y is None.

The python equivalent is:

def scale(x: float, y:Optional[float]=None):
    if y is None:
       y = x

    c_scale_api(x, y)
Asked By: Stuart Axon

||

Answers:

just check it against Py_None.

static PyObject* scale(PyObject* self, PyObject* args) {
    PyObject* input2 = Py_None; // initialize to None
    double input1d, input2d;
    if (!PyArg_ParseTuple(args, "d|O", &input1d, &input2)) {
        return NULL;
    }
    if (input2 == Py_None)
    {
        input2d = input1d;
    }
    else 
    {
        input2d = PyFloat_AsDouble(input2);
    }
    return PyFloat_FromDouble(input2d);
}

this will return the value of the second argument for you to check.

Answered By: Ahmed AEK

You can use | to state that some arguments are optional when parsing the arg tuple. You must ensure that any arguments after the | always have a value before you use them. That is, PyArg_ParseTuple will leave the variables untouched if the argument was not present. Usually, that means you should assign the default values before you call PyArg_ParseTuple. However, because of your requirement, we can just check how many parameters we actually got, and then assign to y if only 1 argument was given.

This code will make the second argument optional, but is not fully equivalent to the python function. That is None is not a valid value for the second argument. So scale(1, 2) and scale(1) are both fine, but scale(1, None) would fail.

static PyObject*
scale(PyObject* self, PyObject* args) // type: METH_VARARGS
{
    double x;
    double y;
    if (!PyArg_ParseTuple(args, "d|d", &x, &y)) {
        return NULL;
    }
    if (PyTuple_GET_SIZE(args) == 1) {
        y = x;
    }
    return PyFloat_FromDouble(x + y);
}

For a fully equivalent C function try the following. Now things like scale(x=1, y=None) will work as well.

static PyObject* 
scale(PyObject* self, PyObject* args, PyObject* kwargs) // type: METH_VARARGS | METH_KEYWORDS
{
    static char* keywords[] = { "x", "y", NULL };

    double x;
    double y;
    PyObject* y_obj;

    if (
        !PyArg_ParseTupleAndKeywords(
            args, kwargs, "d|O:scale", keywords, &x, &y_obj
        )
     ) {
        return NULL;
    }
    if (y_obj == Py_None) {
        y = x;
    } else if (PyFloat_Check(y_obj)) {
        y = PyFloat_AS_DOUBLE(y_obj);
    } else if (PyLong_Check(y_obj)) {
        y = PyLong_AS_LONG(y_obj);
    } else {
        PyErr_SetString(PyExc_TypeError, "y must be float | int | None");
        return NULL;
    }

    return PyFloat_FromDouble(x + y);
}
Answered By: Dunes
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.