jleeothon
jleeothon

Reputation: 3166

True *args and **kwargs in Python C extension

I am developing Python 3 C extension.

Can I get the equivalent or arbitrary positional or keyword arguments?

For instance, in Python, I can write:

def fun(name, parent, *args, **kwargs):
    # do something with name and parent
    # do something with args and kwargs
    pass

But I cannot find of a simple equivalent in C. While we can perfectly write functions with PyObject* args and PyObject* kwargs, I cannot easily "parse out" name and parent from whichever (args/kwargs) it came.

Take:

static PyObject* myFunction(PyObject* self, PyObject* args, PyObject* kwargs) {
    char* kwds[] = {"parent", "name", NULL};
    PyObject* name = NULL;
    PyObject* parent = NULL;
    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwds, &parent, &name)) {
        goto errorParseTupleAndKeywords;
    }
    /* Do something with name and parent */
    /* parent and name maybe have appeared either in args or kwargs */
    /* But I don't have any extra positional (*args) or keyword (**kwargs) here */    
}

A "manual" approach that I could think of looks roughly like:

static PyObject* myFunction(PyObject* self, PyObject* args, PyObject* kwargs) {
    PyObject* name = NULL;
    PyObject* parent = NULL;
    int inKwargs = 0;
    // Pretend to do something with parent
    if (PyDict_GetItemString(kwargs, "parent")) {
        inKwargs++;
        PyDict_DelItemString(kwargs, "parent");
    }
    // Pretend to do something with name
    if (PyDict_GetItemString(kwargs, "name")) {
        inKwargs++;
        PyDict_DelItemString(kwargs, "name");
    }
    // Not sure if -1 works here
    PyObject* newArgs = PyTuple_GetSlice(args, inKwargs, -1); // this is *args
    // the remaining kwargs can be used as **kwargs
}

Upvotes: 4

Views: 2868

Answers (1)

poke
poke

Reputation: 388403

In the C API, PyObject* args really is a Python tuple, and PyObject* kwargs really is a Python dictionary. At least that is what PyArg_ParseTupleAndKeywords internally requires:

int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *keywords, const char *format, char **kwlist, ...)
{
    // …
    if ((args == NULL || !PyTuple_Check(args)) ||
        (keywords != NULL && !PyDict_Check(keywords)) ||
        format == NULL ||
        kwlist == NULL)
    {
        PyErr_BadInternalCall();
        return 0;
    }

    // …
}

The actual implementation of that function in vgetargskeywords also asserts this again, so you should be fine with replacing your PyArg_ParseTupleAndKeywords call with manual extraction from the objects.

This means that you can use both the tuple and dict APIs, or use the iterator protocol to iterate over the items in these objects.

Upvotes: 3

Related Questions