-2

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.

Amir
  • 421
  • 1
  • 4
  • 14
  • "the arguments are not being passed to the function" - Well, what is passed then? This really needs an [mre] - we the emphasis on "reproducible" rather than drip feeding little snippets – DavidW Feb 25 '21 at 19:57
  • Did you find a solution to this problem? – Ruzihm Feb 26 '21 at 16:44
  • 1
    @Ruzihm your answer works for me too (and `PyObject_CallMethodObjArgs` also works for me in exactly the form in the question). The problem is clearly in the code that OP hasn't shown. – DavidW Feb 27 '21 at 16:30
  • @Ruzihm, as you guys mentioned the problem was somewhere else and none of the checks that I had in my code were catching the issue. Both your method and the other method (using CallMethodObjArgs) I suggested works fine now. If you can please add the other method to your answer, I will accept yours. – Amir Mar 01 '21 at 23:56

1 Answers1

2

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
Ruzihm
  • 19,749
  • 5
  • 36
  • 48
  • Thanks a lot. Tried your suggestion and yet didn't work. I see the method ```get_list``` is called (have a print to file statement there). However none of arguments gets a value that is passed to it. – Amir Feb 25 '21 at 19:17
  • BTW, here is ```get_list``` definition line: ```def get_list(self, num_units, maxTime):``` – Amir Feb 25 '21 at 19:19
  • @Amir works for me – Ruzihm Feb 25 '21 at 21:06
  • Your suggestion does work. For the sake of completeness, please update your answer with CallMethodObjArgs and I'll accept your answer. Thanks for your help again. – Amir Mar 01 '21 at 23:58
  • @Amir Glad I could help. I included your code from the answer – Ruzihm Mar 02 '21 at 00:08