Reputation: 3265
I try to implement a Python function using the Python C API, that has a functionality similar to (this implementation is simplified and does not include things like NULL check of keywds):
def my_func(arg1, arg2 = 2, **kwargs):
if arg2 == 1:
# expect certain parameters to be in kwargs
if arg2 == 2:
# expect different parameters to be in kwargs
if arg2 == 3:
# call a Python function with the kwargs (whats in the kwargs is unknown)
PythonFunc(**kwargs)
arg1 and arg2 can be both named and unnamed arguments.
The best I came up with was the following:
PyObject* my_func(PyObject* /*self*/, PyObject* args, PyObject* keywds) {
PyObject* arg1, arg2;
switch(PyTuple_Size(args)) {
case 0:
arg1 = PyDict_GetItemString(keywds, "arg1");
if (arg2) PyDict_DelItemString(keywds, "arg1");
else // throw TypeError
arg2 = PyDict_GetItemString(keywds, "arg2");
if (arg2) PyDict_DelItemString(keywds, "arg2");
break;
case 1:
arg1 = PyTuple_GET_ITEM(args, 0);
if (PyDict_GetItemString(keywds, "arg1")) // throw TypeError
arg2 = PyDict_GetItemString(keywds, "arg2");
if (arg2) PyDict_DelItemString(keywds, "arg2");
break;
case 2:
arg1 = PyTuple_GET_ITEM(args, 0);
if (PyDict_GetItemString(keywds, "arg1")) // throw TypeError
arg2 = PyTuple_GET_ITEM(args, 1);
if (PyDict_GetItemString(keywds, "arg2")) // throw TypeError
break;
default:
// throw TypeError
}
if (...) // parse remaining keywds according to requirements
else // call Python functions using the remaining keyword arguments
}
This can become quite extensive when the functions have more arguments. Is there a simpler way to achieve this behaviour e.g. using PyArg_ParseTupleAndKeywords
?
Upvotes: 0
Views: 390
Reputation: 30912
You can't use PyArg_ParseTupleAndKeywords
directly because it requires all the possible keyword arguments to be set. It doesn't have a "any remaining arguments are keywords" mode.
One option to use it would be to copy/move arg1
and arg2
to another dict and feed that to PyArg_ParseTupleAndKeywords
:
int arg1;
int arg2=2;
char *keywords[] = {"arg1", "arg2", NULL};
PyObject* arg1arg2dict = PyDict_New(); // error check missing...
for (int i=0; keywords[i] != NULL; ++i) {
PyObject* item = PyDict_GetItemString(kwds, keywords[i]);
if (item) {
PyDict_SetItemString(arg1arg2dict, keywords[i], item);
PyDict_DelItemString(kwds, keywords[i]);
}
}
if (!PyArg_ParseTupleAndKeywords(args, arg1arg2dict, "i|i:f", keywords, &arg1, &arg2)) {
Py_DECREF(arg1arg2dict);
return NULL;
}
Py_DECREF(arg1arg2dict);
Obviously that's a bit long but unlike your switch statement it automates well to adding more arguments.
Another option would be to write a loop instead of your switch statement. It's nothing too complicated and again would be easily expandable. In this version you'd avoid PyArg_ParseTupleAndKeywords
(since you'd effectively have implemented a chunk of it)
PyObject* parsed_args[] = {NULL, NULL};
char *keywords[] = {"arg1", "arg2", NULL};
int i=0;
for (; i < PyTuple_Size(args); ++i) {
if (keywords[i] == NULL) {
// run out of possible positional arguments - clean up and raise error
}
if (PyDict_GetItemString(kwds, keywords[i]) {
// clean up and raise an error
}
parsed_args[i] = PyTuple_GET_ITEM(args, i);
Py_INCREF(parsed_args[i]);
}
for (; keywords[i] != NULL; ++i) {
PyObject* item = PyDict_GetItemString(kwds, keywords[i]);
if (item) {
Py_INCREF(item);
parsed_args[i] = item;
PyDict_DelItemString(kwds, keywords[i]);
} else {
// raise error, goto cleanup
}
}
}
The cleanup is just a simple loop of Py_XDECREF
over parsed_args
so can be handled by a shared goto
.
Upvotes: 1