4

I am calling python functions from C++. I was wondering if it is possible to determine the number of parameters and the names of these parameters. I have read the link How to find the number of parameters to a Python function from C? however I do not really understand.

I have this C++ function that calls the function 'add' from pyFunction.py. 'add' takes two parameters and returns the sum.

static float CallPythonFunc( float *parameters )
{
    PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pArgs;
    float ret;

    // Initialize the python interpreter
    Py_Initialize();

    // Make sure we are getting the module from the correct place
    // ### This is where we will put the path input
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append(\"/Developer/IsadoraSDK/IsadoraDemoMathFunction/\")");

    // Build the name object
    // ### This is where we will put the function input
    pName = PyString_FromString("pyFunction");

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

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

    // pFunc is a borrowed reference
    pFunc = PyDict_GetItemString(pDict, "add");

    //
    // Somehow get the number of arguments and possible the arguments names from 'add'
    //

    if (PyCallable_Check(pFunc)) 
    {       
        // Set the number of arguments
                // This is where I would like to pass in number of arguments
        pArgs = PyTuple_New( 2 /*number of arguments*/ );

        //
        // Instead of the following if I had the arguments I could loop through them
        // and pass the correct number in
        //

        // Argument 1
        pValue = PyFloat_FromDouble((double)parameters[0]);
        PyTuple_SetItem(pArgs, 0, pValue);

        // Argument 2
        pValue = PyFloat_FromDouble((double)parameters[1]);
        PyTuple_SetItem(pArgs, 1, pValue);

            // Make the call to the function
        pValue = PyObject_CallObject(pFunc, pArgs);

        // Set return value
        ret = (float)PyFloat_AsDouble(pValue);

        // Clean up
        Py_DECREF(pArgs);
        Py_DECREF(pValue);
    }

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

// Finish the Python Interpreter
Py_Finalize();

return ret;
}

I am really not that familiar with C/C++ so any help would be really helpful. Thanks to everyone for their time!

EDIT: So something like the following?

PyObject *tuple, *arglist;
tuple = PyObject_CallMethod(pFunc,"inspect.getargspec","add");
arglist = PyTuple_GetItem(tuple,0);
int size = PyObject_Size(arglist);
Community
  • 1
  • 1
konbanwa
  • 43
  • 4

1 Answers1

5

This answer to the question you linked to seems to be what you want. inspect.getargspec does exactly what you want on the Python side, and as the answer states, you can use PyObject_CallMethod or one of the related functions described at that link target to call inspect.getargspec from your C++ code, get the returned tuple as a PyObject, use PyTuple_GetItem(returned_tuple, 0) to get the argument list, and then use either PyObject_Size() or PyObject_Length() on the list to get the number of arguments. You'll also want to check the second and third elements of the returned tuple and increment the number of arguments by 1 for each of the two that is not Py_None. See the below code snippet for why.

>>> import inspect
>>> def testfunc(a, b, c, *d, **e):
    pass

>>> inspect.getargspec(testfunc)
ArgSpec(args=['a', 'b', 'c'], varargs='d', keywords='e', defaults=None)

Here's an example of what you should do (not all possible errors may be checked for, but it should be all of the NULL checks that may be necessary):

PyObject *pName, *pInspect, *argspec_tuple, *arglist;
int size;

pName = PyString_FromString("inspect");

if (pName)
{
    pInspect = PyImport_Import(pName);
    Py_DECREF(pName);


    if (pInspect)
    {
        pName = PyString_FromString("getargspec");

        if (pName)
        {
            argspec_tuple = PyObject_CallMethodObjArgs(pInspect, pName, pFunc, NULL);
            Py_DECREF(pName);

            if (argspec_tuple)
            {
                arglist = PyTuple_GetItem(argspec_tuple, 0);

                if (arglist)
                {
                    size = PyObject_Size(arglist)
                         + (PyTuple_GetItem(argspec_tuple, 1) == Py_None ? 0 : 1)
                         + (PyTuple_GetItem(argspec_tuple, 2) == Py_None ? 0 : 1);  // Haven't actually tested this, but it should work
                }
            }
        }
    }
}
Community
  • 1
  • 1
JAB
  • 20,783
  • 6
  • 71
  • 80
  • Thank you for your response. I also edited my post for a follow up question – konbanwa Jul 08 '11 at 17:50
  • In addition to what you put in your edit, you'd want to include something like `size += (PyTuple_GetItem(tuple, 1) == Py_None ? 0 : 1) + (PyTuple_GetItem(tuple, 2) == Py_None ? 0 : 1);` to account for the possibility of `*` and `**` arguments. (If you don't recognize the syntax used, it's called the ternary operator and is similar to an in-line `if...else` statement.) – JAB Jul 08 '11 at 18:11
  • Oh, actually, check my updated answer for what you might want to do. – JAB Jul 08 '11 at 18:40
  • Oh, just noticed as well that in Python 3+, you'd want to use `getfullargspec` rather than `getargspec`, as the latter is deprecated since version 3.0. The syntax wouldn't change here due to the starting elements of the tuple returned by `getfullargspec` being the same as the elements of the tuple returned by `getargspec`, but this is especially important if your Python function has keyword-only arguments or annotations, as `getargspec` fails when used on a function with such. This also means you'd need to check for such arguments the same way you checked the size of the normal argument list. – JAB Jul 08 '11 at 18:54