Calling a python method from C/C++, and extracting its return value

96,045

Solution 1

As explained before, using PyRun_SimpleString seems to be a bad idea.

You should definitely use the methods provided by the C-API (http://docs.python.org/c-api/).

Reading the introduction is the first thing to do to understand the way it works.

First, you have to learn about PyObject that is the basic object for the C API. It can represent any kind of python basic types (string, float, int,...).

Many functions exist to convert for example python string to char* or PyFloat to double.

First, import your module :

PyObject* myModuleString = PyString_FromString((char*)"mytest");
PyObject* myModule = PyImport_Import(myModuleString);

Then getting a reference to your function :

PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"myabs");
PyObject* args = PyTuple_Pack(1,PyFloat_FromDouble(2.0));

Then getting your result :

PyObject* myResult = PyObject_CallObject(myFunction, args)

And getting back to a double :

double result = PyFloat_AsDouble(myResult);

You should obviously check the errors (cf. link given by Mark Tolonen).

If you have any question, don't hesitate. Good luck.

Solution 2

Here is a sample code I wrote (with the help of various online sources) to send a string to a Python code, then return a value.

Here is the C code call_function.c:

#include <Python.h>
#include <stdlib.h>
int main()
{
   // Set PYTHONPATH TO working directory
   setenv("PYTHONPATH",".",1);

   PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *presult;


   // Initialize the Python Interpreter
   Py_Initialize();


   // Build the name object
   pName = PyString_FromString((char*)"arbName");

   // Load the module object
   pModule = PyImport_Import(pName);


   // pDict is a borrowed reference 
   pDict = PyModule_GetDict(pModule);


   // pFunc is also a borrowed reference 
   pFunc = PyDict_GetItemString(pDict, (char*)"someFunction");

   if (PyCallable_Check(pFunc))
   {
       pValue=Py_BuildValue("(z)",(char*)"something");
       PyErr_Print();
       printf("Let's give this a shot!\n");
       presult=PyObject_CallObject(pFunc,pValue);
       PyErr_Print();
   } else 
   {
       PyErr_Print();
   }
   printf("Result is %d\n",PyInt_AsLong(presult));
   Py_DECREF(pValue);

   // Clean up
   Py_DECREF(pModule);
   Py_DECREF(pName);

   // Finish the Python Interpreter
   Py_Finalize();


    return 0;
}

Here is the Python code, in file arbName.py:

 def someFunction(text):
    print 'You passed this Python program '+text+' from C! Congratulations!'
    return 12345

I use the command gcc call_function.c -I/usr/include/python2.6 -lpython2.6 ; ./a.out to run this process. I'm on redhat. I recommend using PyErr_Print(); for error checking.

Solution 3

A complete example of calling a Python function and retrieving the result is located at http://docs.python.org/release/2.6.5/extending/embedding.html#pure-embedding:

#include <Python.h>

int
main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pDict, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyString_FromString(argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyInt_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                /* pValue reference stolen here: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyInt_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    Py_Finalize();
    return 0;
}

Solution 4

To prevent the extra .py file as in the other answers, you can just retrieve the __main__ module, which is created by the first call to PyRun_SimpleString:

PyObject *moduleMainString = PyString_FromString("__main__");
PyObject *moduleMain = PyImport_Import(moduleMainString);

PyRun_SimpleString(
    "def mul(a, b):                                 \n"\
    "   return a * b                                \n"\
);

PyObject *func = PyObject_GetAttrString(moduleMain, "mul");
PyObject *args = PyTuple_Pack(2, PyFloat_FromDouble(3.0), PyFloat_FromDouble(4.0));

PyObject *result = PyObject_CallObject(func, args);

printf("mul(3,4): %.2f\n", PyFloat_AsDouble(result)); // 12

Solution 5

You have to extract the python method somehow and run it with PyObject_CallObject(). To do that, you can provide Python a way to set the function, as the Extending and Embedding Python Tutorial example does.

Share:
96,045

Related videos on Youtube

D R
Author by

D R

Updated on January 21, 2022

Comments

  • D R
    D R over 2 years

    I'd like to call a custom function that is defined in a Python module from C. I have some preliminary code to do that, but it just prints the output to stdout.

    mytest.py

    import math
    
    def myabs(x):
        return math.fabs(x)
    

    test.cpp

    #include <Python.h>
    
    int main() {
        Py_Initialize();
        PyRun_SimpleString("import sys; sys.path.append('.')");
        PyRun_SimpleString("import mytest;");
        PyRun_SimpleString("print mytest.myabs(2.0)");
        Py_Finalize();
    
        return 0;
    }
    

    How can I extract the return value into a C double and use it in C?

    • user1066101
      user1066101 almost 14 years
      Have you read this: docs.python.org/c-api? It seems to answer your question. docs.python.org/c-api/number.html#PyNumber_Float seems to be what you're looking for. What's wrong with it? What more do you need?
    • D R
      D R almost 14 years
      The question really is how to access the returned object from mytest.myabs(2.0). Once I have a pointer to it, I can easily convert it to a float using the PyNumber_Float function.
    • Matt Joiner
      Matt Joiner almost 14 years
      Can we just see a answer with a code example and be done with it?
  • D R
    D R almost 14 years
    I will accept your answer because of the useful tips, but it would have been more helpful to me if you could provide a complete working C module calling the above python function.
  • Palec
    Palec almost 10 years
    Calling a.out would be better as ./a.out because your call to a.out relies on working directory being in PATH, which is not a very common configuration. You should also consider providing -o option to GCC to give the executable a better name.
  • Palec
    Palec almost 10 years
    See How does editing work? on Meta SE. If you can get to the revision history but could not see the changes in markup, just switch the diff view to side-by-side markdown. There are buttons at the top of each revision in revision history that switch diff view. If you do not understand what the markup does, refer to editing help. If you’re editing a post, the editor has built-in help – see the orange question mark to the top right of the editor.
  • jhegedus
    jhegedus over 8 years
    this gives me a segfault on python 2.7 :(
  • andrew
    andrew over 8 years
    Compilation line (for c++) : g++ call_function.cpp python2.7-config --cflags python2.7-config --ldflags -o call_function
  • Translunar
    Translunar over 8 years
    If you're calling an object's class method, do you need to pass that object in the args?
  • Antonello
    Antonello about 8 years
    Where is the boost part in this script ?
  • user4815162342
    user4815162342 over 7 years
    Although it is accepted, this answer is unfortunately not a good template for calling Python code (and I've seen it referenced elsewhere). The PyTuple_Pack line leaks a float object each time it is executed. The function can be called without manual conversion and tuple creation, with result = PyObject_CallFunction(myFunction, "d", 2.0). The import can be written as module = PyImport_ImportModule("mytest") (without creating a Python string).
  • user13107
    user13107 almost 6 years
    Please see my followup question here stackoverflow.com/questions/50441365/…
  • messy212
    messy212 almost 6 years
    I just had some troubles with this code, I didn't succeed to import my code, so all there is to do is add PyRun_SimpleString("import sys; sys.path.append('.')"); and then use the PyObject method. thanks for this code, it work now.
  • nice_remark
    nice_remark over 5 years
    Does this work for calling a function that is defined within a class?
  • e-info128
    e-info128 over 5 years
    In python3 replace PyString_FromString to PyUnicode_FromString and Py_Finalize to Py_FinalizeEx.
  • mauriii
    mauriii almost 5 years
    the reason for why a "z" was used in pValue=Py_BuildValue("(z)",(char*)"something"); is it represents a Python str/null object and a C++ char*. For other arg types look at docs.python.org/3.6/c-api/arg.html
  • applemonkey496
    applemonkey496 over 3 years
    If you're using Python >3.5, PyString_FromString() is PyUnicode_FromString().
  • fisakhan
    fisakhan over 3 years
    I receive the following error: ‘PyString_FromString’ was not declared in this scope PyObject *path = PyString_FromString(scriptDirectoryName);