9

We're working on some Python/C-API code, and we've encountered a method that would like to be passed a callback. The method will ship periodic updates to the callback as a form a feedback. As it turns out, we're not that interested in periodic feedback. The only way to disable the method's default feedback mechanism is to pass it some kind of callback.

The technique we've employed is to declare a module level function that just returns None, ie:

static PyObject*
donothing(PyObject* self, PyObject* args) {
    return Py_None;
    }

But of course, this function also needs to be registered with the modules method table, ie:

static PyMethodDef methods[] = {
    {"donothing", donothing, METH_VARARGS, "do nothing"},
     ...
    {NULL}
    };

Then, when we go to call the method, we need to grab a reference to this method, ie: PyObject_GetAttrString(module_reference, "donothing").

All of this feels like we're spending too much time spinning our wheels just to do nothing. Then it ocurred to me..hey, seems like a perfect use for lambda x: None. But after spending an hour with the Python/C-API docs, I can't figure out how one create's lambdas.

I see there are references to closures on the page http://docs.python.org/2/c-api/function.html, but I can't sort out the details on how one creates them.

Any pointers (or references to RTFM) would be greatly appreciated.

user590028
  • 11,364
  • 3
  • 40
  • 57
  • 2
    could you use `builtins.id` instead of concocting your own do-nothing function? – SingleNegationElimination Oct 23 '13 at 18:05
  • In this link(http://www.secnetix.de/olli/Python/lambda_functions.hawk) you can find more lambda examples. – randiel Oct 23 '13 at 18:06
  • The idea of using __builtins__.id() saves us polluting our module with silly donothing() methods (which is why I up-voted it)...but I was really hoping for some pointers on creating lambda's using the Python/C-API. – user590028 Oct 23 '13 at 19:13
  • 2
    Why don't you use something like: `f = PyRun_String("lambda x: None", Py_single_input, globals, locals);`. I guess the creation of the function object is not time limiting. Evaluating python code from C is sometimes the easiest solution (and most readable as well). – dastrobu Oct 23 '13 at 22:25
  • This is a really good idea. I poked around the docs on this method, and found **Py_CompileStringFlags()**. This may in fact be the way one is suppose to create CodeObjects, then to associate them as arbitrary attributes of a type. Thanks for the tip! – user590028 Oct 24 '13 at 11:17
  • Your code has a reference counting bug. You must Py_INCREF() Py_None or use the Py_RETURN_NONE macro. – Christian Heimes Oct 24 '13 at 17:22

1 Answers1

7

A lambda expressions is used to create simple anonymous functions. These have a PyFunction_Type wrapping an object of PyCode_Type, which is a chunk of executable code. But you're already on the C side, so creating a Python function would be a little too much. Instead you should create an object of PyCFunction_Type. This is similar to what you've tried to do with the module methods.

The boilerplate in C wouldn't be too big either, but only a few lines:

static PyObject *
donothing(PyObject *self, PyObject *args) {
    Py_RETURN_NONE;
}
static PyMethodDef donothing_ml = {"donothing", donothing, METH_VARARGS, "doc"};

The object then is created with PyCFunction_New(&donothing_ml, NULL) which yields a <built-in function donothing>. This function is independent of your module and can be used like any other PyObject.

It's not exactly a high level lambda, but rather a low level implementation of lambda *args: None.


However if you'd really like to create a high level lambda you can do this with a single statement like dastrobu proposed

l = PyRun_String("lambda *args: None", Py_eval_input, PyEval_GetGlobals(), NULL);

or if you'd like to assemble it yourself you could do

PyCodeObject *c = (PyCodeObject *) Py_CompileString("None", "fn", Py_eval_input);
#if PY_MAJOR_VERSION >= 3
c->co_name = PyUnicode_FromString("<c-lambda>"); // function name
#else
c->co_name = PyString_FromString("<c-lambda>"); // function name
#endif
c->co_flags |= CO_VARARGS; // accept *args
c->co_nlocals = 1; // needed in Python 3
l = PyFunction_New((PyObject *) c, PyEval_GetGlobals());

In both cases you'll get a function with dissasembled code dis(l) equivalent to a lambda:

  1           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE        
tynn
  • 38,113
  • 8
  • 108
  • 143