tags:

views:

40

answers:

1

Is it possible to load a python function from a string and then call that function with arguments and get the return value?

I'm using the python C API to run python code from inside my C++ application. I'm able to load a module from a file using PyImport_Import, get a function object from that using PyObject_GetAttrString, and call the function with PyObject_CallObject. What I'd like to do is to load the module/function from a string instead of a file. Is there some equivalent to PyImport_Import which would allow me to pass it a string instead of a file? I need to pass arguments to the function I'm calling and I need access to the return value, so I can't just use PyRun_SimpleString.


Edit:

I found this solution after getting turned on to PyRun_String. I'm creating a new module, getting its dictionary object, passing that along in a call to PyRun_String to define a function in my new module, then getting a function object for that newly created function and calling it via PyObject_CallObject, passing my args. This is what I've found to solve my problem: main.cpp


int main()
{
    PyObject *pName, *pModule, *pArgs, *pValue, *pFunc;
    PyObject *pGlobal = PyDict_New();
    PyObject *pLocal;

    //Create a new module object
    PyObject *pNewMod = PyModule_New("mymod");

    Py_Initialize();
    PyModule_AddStringConstant(pNewMod, "__file__", "");

    //Get the dictionary object from my module so I can pass this to PyRun_String
    pLocal = PyModule_GetDict(pNewMod);

    //Define my function in the newly created module
    pValue = PyRun_String("def blah(x):\n\tprint 5 * x\n\treturn 77\n", Py_file_input, pGlobal, pLocal);
    Py_DECREF(pValue);

    //Get a pointer to the function I just defined
    pFunc = PyObject_GetAttrString(pNewMod, "blah");

    //Build a tuple to hold my arguments (just the number 4 in this case)
    pArgs = PyTuple_New(1);
    pValue = PyInt_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    //Call my function, passing it the number four
    pValue = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    printf("Returned val: %ld\n", PyInt_AsLong(pValue));
    Py_DECREF(pValue);

    Py_XDECREF(pFunc);
    Py_DECREF(pNewMod);
    Py_Finalize();

    return 0;
}

Here is the rest of my original post, left for posterity:

Here's what I was doing originally: main.cpp:


#include <Python.h>

int main()
{
    PyObject *pName, *pModule, *pArgs, *pValue, *pFunc;

    Py_Initialize();
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('')");
    pName = PyString_FromString("atest");
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if(pModule == NULL)
    {
        printf("PMod is null\n");
        PyErr_Print();
        return 1;
    }

    pFunc = PyObject_GetAttrString(pModule, "doStuff");
    pArgs = PyTuple_New(1);
    pValue = PyInt_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    pValue = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    printf("Returned val: %ld\n", PyInt_AsLong(pValue));
    Py_DECREF(pValue);

    Py_XDECREF(pFunc);
    Py_DECREF(pModule);

    Py_Finalize();

    return 0;
}

And atest.py:


def doStuff( x):
    print "X is %d\n" % x
    return 2 * x
+1  A: 

PyRun_String in the Python C API is probably what you're looking for. See: http://docs.python.org/c-api/veryhigh.html

Tamás
It's not clear how I would pass arguments to the function. It does look like I can define a function using `PyRun_String`, but I don't see how to call it with parameters that aren't just hard-coded string. That is, I could call PyRun_String again with "doStuff(7)" as the first argument, but that won't work if the argument is an object or something like that. Is there some way to call PyObject_GetAttrString to get the function object for doStuff if it's been defined via PyRun_String?
idontwanttortfm
Accepting because it started me on the path to finding a solution, though I had to do a _lot_ of reading to get there. Thanks.
idontwanttortfm
Sorry, I wasn't online for the last few hours, but it looks like you figured it out exactly. `PyRun_String` runs a block of Python code from a string and returns whatever the code would have returned -- I presume it's `None` in your case. After that, the function you've just evaluated is added to the local namespace -- you have to retrieve it from there using `PyObject_GetAttrString` and then call it. You might also want to check the return value of `PyRun_String` - if it is `NULL`, there was an exception while executing the code block.
Tamás