How can I create lambda PyObject from its Python code in a C string?

101 views Asked by At

I have the following code in Python, which uses pyjexl module:

import pyjexl

jexl = pyjexl.JEXL()
jexl.add_transform("lowercase", lambda x: str(x).lower())

I want to do the same using Python C API. Something like this:

Py_Initialize();
PyObject* pyjexlModule = PyImport_ImportModule("pyjexl");
PyObject* jexl = PyObject_CallMethod(pyjexlModule, "JEXL", NULL);

const char* myLambda = "lambda x: str(x).lower()";
PyObject* lambda = ... /* something using myLambda */
PyObject_CallMethod(jexl, "add_transform", "sO", "lowercase", lambda);

(Simplified version of the code, e.g., NULL checking and Py_XDECREF() have been omitted)

The problem I'm trying to solve is how to get a PyObject representing a lambda function which Python code is contained in the C-string myLambda.

How can I achieve this?

I have tried with the suggestion by @DavidW using this:

PyObject* globals = PyDict_New();
PyObject* locals = PyDict_New();
PyObject* lambda = PyRun_String(myLambda, Py_eval_input, globals, locals);

But I think it's not working, as the resulting lambda variable (inspected using debugger) is of type PyNone:

Debugger screenshot

1

There are 1 answers

4
DavidW On

PyRun_String:

PyObject *lambda = PyRun_String(lambda, Py_eval_input, some_dict, some_dict);

where some_dict can be an empty dict. (I think on some older versions of Python you'd need to have str in the dict, so might need to use PyEval_GetBuiltins but this should be unnecessary now).


The exact function I tested with was

PyObject *makeLambda() {
        const char* myLambda = "lambda x: str(x).lower()";
        PyObject* globals = PyDict_New();
        PyObject* locals = PyDict_New();
        PyObject* lambda = PyRun_String(myLambda, Py_eval_input, globals, locals);
        return lambda;
    }

Note that I've omitted error handling for PyDict_New() - ideally there should be a NULL check after each of these.

In order to test it I built it into a Cython module - that's just because it provides an easy way to compile and call C functions so it's a quick way to test it:

cdef extern from *:
    """
    PyObject *makeLambda() {
        const char* myLambda = "lambda x: str(x).lower()";
        PyObject* globals = PyDict_New();
        PyObject* locals = PyDict_New();
        PyObject* lambda = PyRun_String(myLambda, Py_eval_input, globals, locals);
        return lambda;
    }
    """
    object makeLambda()

def callMakeLambda():
    return makeLambda()

The Cython module is compiled with cythonize -if modulename.pyx

You can then test it with

>>> import modulename
>>> modulename.callMakeLambda()

The second line returns <function <lambda> at 0x7f72c0d8bc40> (the exact address of the lambda will vary of course).