Search code examples
pythonc++embedding

Getting Python function argument names in C++


I'm trying to get the argument names of a Python function called from C++. A little background:

I have a C++ application that needs to call Python functions, passing them arguments by name. So far I have been able to do this by parsing the Python module's .py file; however I would like to be able to handle .pyc files as well.

Ideally this would be done via the Python C API (rather than trying to decompile the bytecode), but I can't see any obvious way of doing this. There are ways of doing this in Python e.g. with inspect but not in C++.

Anyone know of a possible solution? TIA.


Solution

  • You said that you would use the inspect module if you wanted to do this from python. Why not call the inspect module from the C API?

    The following C code prints all arguments of a python function. It should be valid C++ code as well. I dont use the Python C API very often, so please tell me if some things could be improved.

    #include <Python.h>
    #include <stdio.h>
    
    int main(int argc, char* argv[])
    {
        wchar_t* const program = Py_DecodeLocale(argv[0], nullptr);
        Py_SetProgramName(program);
        Py_Initialize();
    
        // Define a python function f with argument names x and y.
        // >>> def f(x, y): return x*y
        PyRun_SimpleString("def f(x, y): return x*y\n");
    
        // Get the function as python object.
        // >>> import sys
        // >>> f_function = sys.modules["__main__"].f
        PyObject* const sys_module_name = PyUnicode_DecodeFSDefault("sys");
        PyObject* const sys_module = PyImport_Import(sys_module_name);
        PyObject* const modules_dict = PyObject_GetAttrString(sys_module, "modules");
        PyObject* const main_name = PyUnicode_DecodeFSDefault("__main__");
        PyObject* const main_module = PyDict_GetItem(modules_dict, main_name);
        PyObject* const f_function = PyObject_GetAttrString(main_module, "f");
    
        // Get the inspect.getargspec function.
        // >>> import inspect
        // >>> getargspec_function = inspect.getargspec
        PyObject* const inspect_module_name = PyUnicode_DecodeFSDefault("inspect");
        PyObject* const inspect_module = PyImport_Import(inspect_module_name);
        PyObject* const getargspec_function = PyObject_GetAttrString(inspect_module, "getargspec");
    
        // Call the inspect.getargspec function.
        // >>> argspec = getargspec_function(f_function)
        PyObject* const argspec_call_args = PyTuple_New(1);
        PyTuple_SetItem(argspec_call_args, 0, f_function);
        PyObject* const argspec = PyObject_CallObject(getargspec_function, argspec_call_args);
    
        // Get args from argspec.
        // >>> f_args = argspec.args
        PyObject* const f_args = PyObject_GetAttrString(argspec, "args");
    
        // f_args now holds a python list with all arguments of f.
    
        // As example usage, you can print the arguments:
        // >>> for i, a in enumerate(f_args):
        // ...     print("Repr of arg", i, "is", repr(a))
        Py_ssize_t const num_args = PyList_Size(f_args);
        for (Py_ssize_t i = 0; i < num_args; ++i)
        {
            PyObject* const arg = PyList_GetItem(f_args, i);
            PyObject* const arg_repr = PyObject_Repr(arg);
            PyObject* const arg_str = PyUnicode_AsASCIIString(arg_repr);
            char const* const arg_c_str = PyBytes_AS_STRING(arg_str);
            printf("Repr of arg %ld is %s\n", i, arg_c_str);
        }
    
        Py_Finalize();
        PyMem_RawFree(program);
    
        return 0;
    }