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)
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.
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);
}
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)
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.
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);
}