Brandon Ogle
Brandon Ogle

Reputation: 715

How to create and return a function object in a c extension module?

I am trying to write a python extension module where some of the functions are curried, but I am not quite sure how to go about doing so. The main difficulty being that I am not sure how to create and return a PyFunction object and how to then pass it the parse rules for its arguments. Is there a fairly efficient way to do this or is this insanity?

From the python side the desired semantics would be:

# given a function f(x, y)
f(a, b) -> result
f(a)    -> f'
f'(b)   -> result

Upvotes: 0

Views: 376

Answers (1)

tynn
tynn

Reputation: 39843

Let's have a look at a possible Python implementation first.

def f(x, y=None):
    if y is None:
        return lambda y: f(x, y)
    return 'result'

The only thing which needs to be done in C here is creating the lambda function somehow. Here we have the issue not knowing about the PyCFunction calling the C function itself. So we have to write wrapper around this and creates a new PyCFunction object.

static PyObject* curried (PyObject *old_args, PyObject *new_args);
static PyMethodDef curried_def = {"curried", curried, METH_VARARGS, "curried"};

static PyObject* f (PyObject *self, PyObject *args) {
    PyObject *x = NULL, *y = NULL;
    if(!PyArg_ParseTuple(args, "O|O", &x, &y))
        return NULL;

    // validate x
    if (y == NULL)
        return Py_INCREF(args), PyCFunction_New(&curried_def, args);
    // validate y

    // do something to obtain the result
    return result;
}

static PyObject* curried (PyObject *old_args, PyObject *new_args) {
    Py_ssize_t old_args_count = PyTuple_Size(old_args);
    Py_ssize_t new_args_count = PyTuple_Size(new_args);
    PyObject *all_args = PyTuple_New(old_args_count + new_args_count);
    Py_ssize_t i;
    PyObject *o;
    for (i = 0; i < old_args_count; i++) {
        o = PyTuple_GET_ITEM(old_args, i);
        Py_INCREF(o);
        PyTuple_SET_ITEM(all_args, i, o);
    }
    for (i = 0; i < new_args_count; i++) {
        o = PyTuple_GET_ITEM(new_args, i);
        Py_INCREF(o);
        PyTuple_SET_ITEM(all_args, old_args_count + i, o);
    }
    return f(NULL, all_args);
}

This yields the desired semantics of

f(a, b) -> result
f(a) -> <built-in method curried of tuple object at 0x123456>
f(a)(b) -> result

Here we abuse the PyCFunction type a little and the second parameter passed to PyCFunction_New(&curried_def, args) is supposed to be the self object this function is bound to, hence we'll get a built-in method curried of tuple object. If you need the self parameter of the original function or use keyword arguments, you'd have to extend this hack a little and build a custom object to pass instead of the args. Also it's possible to create a type like of PyCFunction for curried functions. As far as I know, there isn't anything like that yet.

Upvotes: 1

Related Questions