Search code examples
pythonc++numpypython-embedding

When embedding python in another application how do I import or call a function in a submodule (i.e. scipy.optimize.nnls)?


First I'll restate the question: When embedding Python into a C/C++ application using python docs and other resources. I discovered that you can import a module using

PyObject *pName = PyUnicode_FromString((char*)"scipy");
PyObject *pModule = PyImport_Import(pName); 

but if I try to import "scipy.optimize"

PyObject *pName = PyUnicode_FromString((char*)"scipy.optimize");
PyObject *pModule = PyImport_Import(pName); 

then the program fails to initialize pModule. Again if I try

PyObject *pName = PyUnicode_FromString((char*)"scipy");
PyObject *pModule = PyImport_Import(pName); 
pFunc = PyObject_GetAttrString(pModule, (char*)"optimize.nnls");

where I put optimize in the function name the it fails to initialize pFunc. How do I import or call a function in a submodule, i.e. how do I call the function scipy.optimize.nnls?

Next, I'll lay out my code in case it helps:

/* Relelvant imports
    #include <Python.h>
    #include <numpy/arrayobject.h> 
*/

void nnls::update(const vec& x, const vec& y)
{
    mat B;
    vecToGslVec(x, gslx);
    generateX(X, gslx);
    A = gslMatToMat(X);
    B = A.transpose();
    long double *c_out;

    Py_Initialize();
    PyObject *pName = PyUnicode_FromString((char*)"scipy"); //Issue Here
    check(pName, "pName not initializes.");

    PyObject *pModule = PyImport_Import(pName);
    check(pModule, "pModule not initializes.");

    PyObject *pFunc, *pArgs, *pResult;
    PyArrayObject *pNpArray;

    npy_intp Adims[2]; Adims[0] = A.rows(); Adims[1] = A.cols();
    npy_intp bdim[1];  bdim[0] = y.size();

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, (char*)"optimize.nnls"); //Issue Here
        check(pFunc, "pFunc not initializes.");

        /*program never advances past this point unless I remove this check, 
        in which case I get a segfault, because pFunc is not initialized.*/

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_Pack(2,
                PyArray_SimpleNewFromData(2, Adims, NPY_FLOAT, B.data()),
                PyArray_SimpleNewFromData(1, bdim , NPY_FLOAT, \
                    const_cast<double*> (y.data()))
            );
            check(pArgs, "pArgs not initializes.");
            pResult = PyObject_CallObject(pFunc, pArgs);
            check(pResult, "pResult not initializes.");
            pNpArray = reinterpret_cast<PyArrayObject*>(pResult);
            log_info("not PyArray cast");
        }
        Py_DECREF(pFunc);
    }
    Py_DECREF(pModule);
    Py_DECREF(pArgs);
    c_out = reinterpret_cast<long double*>(PyArray_DATA(pNpArray));
    for(size_t i=0; i<order; i++){
        gsl_vector_set(gslc, i, c_out[i]);
    }
    if (pResult != NULL) Py_DECREF(pResult); Py_DECREF(pNpArray);
    Py_Finalize();
}

Again, My issue is in the lines

PyObject *pModule = PyImport_Import(pName);

and

pFunc = PyObject_GetAttrString(pModule, (char*)"optimize.nnls");

If I set

pName = PyUnicode_FromString((char*)"scipy.optimize"); 

or

pFunc = PyObject_GetAttrString(pModule, (char*)"optimize.nnls");

the code fails to initialized these variables at these points.

So to reiterate my question one last time how do I import the module scipy.optimize or call the function optimize.nnls in this context? Thanks. I hope it isn't too confusing, if it is let me know and I'll clarify.


Solution

  • First of all, I've never use PyImport_Import(). I've always used PyImport_ImportModule(), which takes a const char * arg instead of a PyObject *. That should save some steps in your code.

    PyObject *pModule = PyImport_ImportModule("scipy.optimize");
    PyObject *pFunc = PyObject_GetAttrString(pModule, "nnls");
    

    This is how I have done things in code I have written. PyImport_ImportModule() handles package hierarchies. PyObject_getAttrString() does not know what to do with the dot.

    I have never used PyImport_Import(), so I don't know how its behavior might change things.