Search code examples
pythonc++python-c-api

Can't pass arguments to Python function while embedding Python in C++


I'm embedding a Python module in my C++ code and I am using Python/C API. I need to call a Python module function and get the results. The function gets an unsigned integer and a double as input arguments and outputs a list. I use the following to call the function:

unsigned int num_units = 10;
double max_time = 15.12;
PyObject *output_list = PyObject_CallMethod(sample_object, "get_list", 
                                            "(I)", num_units, "(d)", max_time);

What I notice from Python side when adding print statements inside get_list function is that the arguments are not being passed to the function. I think I'm not getting the syntax right maybe?

Update: I tried @Ruzihm's suggestion with the following syntax initially without success. It turns out that there were other syntactical issues in the python code which prevented proper execution of the code and none of my error checks were catching it. After fixing the issues, the code ran flawlessly.

PyObject *output_list = PyObject_CallMethod(sample_object, "get_list",
                                            "Id",num_units, max_time);

Also I tried using CallMethodObjArgs with the following code which worked as well:

PyObject *func_obj, *num_units_obj, *max_time_obj;

num_units_obj = PyLong_FromUnsignedLong(num_units);
max_time_obj = PyFloat_FromDouble(max_time);
func_obj = PyUnicode_FromString("get_list");

PyObject *output_list = PyObject_CallMethodObjArgs(sample_object, 
                                                   func_obj, num_units_obj,
                                                   max_time_obj, NULL);

Py_DECREF(func_obj);
Py_DECREF(num_units_obj);
Py_DECREF(max_time_obj);

Also the Python function definition looks like this:

def get_list(self, num_units, max_time):

Any help is very much appreciated.


Solution

  • There should be exactly one format string (which may be NULL) followed by any/all inputs. Parenthesis are for specifying tuples of size zero or one, which you don't mention needing. So, just do this:

    PyObject *output_list = PyObject_CallMethod(sample_object, 
            "get_list", "Id", num_units, max_time);
    

    Full example:

    foobar.py

    class foobar():
         def get_list(self, num_units, max_time):
             print(num_units)
             print(max_time)
    

    foobar.cpp

    #define PY_SSIZE_T_CLEAN
    #include "python3.6m/Python.h"
    #include <iostream>
    
    int main() {
        PyObject *module, *dict, *python_class, *sample_object; 
    
        setenv("PYTHONPATH", ".", 1);
        Py_Initialize();  
    
        module = PyImport_ImportModule("foobar");
        if (module == nullptr)
        {
            std::cout << "Failed to import module.";
            return 1;
        }
     
        dict = PyModule_GetDict(module);
        if (dict == nullptr)
        {
            std::cout << "Failed to get module dict.";
            return 1;
        }
        Py_DECREF(module);
     
        python_class = PyDict_GetItemString(dict, "foobar");
        if (python_class == nullptr)
        {
            std::cout << "Failed to get class.";
            return 1;
        }
        Py_DECREF(dict);
     
        sample_object = PyObject_CallObject(python_class, nullptr);
        if (sample_object == nullptr)
        {
            std::cout << "Failed to instantiate object.";
            return 1;
        }
        Py_DECREF(python_class);
     
    
        unsigned int num_units = 10;
        double max_time = 15.12;
     
        PyObject *output_list = PyObject_CallMethod(sample_object, "get_list",
                                                 "Id", num_units, max_time);
    
        /* Alternatively, use PyObject_CallMethodObjArgs
        PyObject *func_obj, *num_units_obj, *max_time_obj;
    
        num_units_obj = PyLong_FromUnsignedLong(num_units);
        max_time_obj = PyFloat_FromDouble(max_time);
        func_obj = PyUnicode_FromString("get_list");
    
        PyObject *output_list = PyObject_CallMethodObjArgs(sample_object, 
                                                       func_obj, num_units_obj,
                                                       max_time_obj, NULL);
    
        Py_DECREF(func_obj);
        Py_DECREF(num_units_obj);
        Py_DECREF(max_time_obj);
        */
    }
    

    (Based on this answer's code)

    Output

    $ g++ foobar.cpp -lpython3.6m;./a.out
    10
    15.12