4

I am making Python code available to a C++ library using Boost::Python. I have a template function that converts C++ types to Python types:

template <typename T> bp::object convert(T v);

that is specialized for various primitive types as well as some templated classes. One such class is an N-dimensional Array, for which I have a function to convert to a NumPy Array. I'd like to use this function in a corresponding convert specialization, e.g.:

template <typename Y> bp::object convert(NDArray<Y> v);

My primary issue is that this conversion function then needs to live in a header since it's templated, but it uses NumPy's PyArray_ functions, which require import_array() to be called prior to usage. import_array() is currently called in the constructor for a singleton object whose purpose is to provide access to Python functions. It appears this won't work because, by default, #include <numpy/arrayobject.h> only makes the PyArray_ functions available to the current compilation unit. I've tried defining a PY_ARRAY_UNIQUE_SYMBOL and defining NO_IMPORT_ARRAY for the header, but this doesn't prevent PyArray_ functions from segfaulting.

Here's a simplified representation of my code, which segfaults upon using a PyArray_ function in the "conversions.h" header:

"conversions.h":

#include <boost/python.hpp>
#include <numpy/numpyconfig.h>
#include <numpy/arrayobject.h>

namespace bp = boost::python;

template <typename T> bp::object convert(T v);
template <> bp::object convert<int>(int v) { return bp::long_(v); }
...
template <typename Y> bp::object convert(NDArray<Y> v)
{
... use PyArray_ functions to create and return a NumPy array
... segfaults here!
}

"Bridge.h":

#include "conversions.h"

class Bridge {
public:
    static Bridge* instance();

    // c++11 variadic template (parameter pack)
    template <typename... Args> void exec(Args... args)
    {
        ...
        fn(convert(args)...); // fn is a Python function
        ...
    }
    void foo();

private:
    Bridge();
    Bridge(const Bridge&);
    void operator=(const Bridge&);
    static Bridge* instance_;
}

"Bridge.cpp":

#include "Bridge.h"
#include <numpy/numpyconfig.h>
#include <numpy/arrayobject.h>

Bridge* Bridge::instance_ = nullptr;
Bridge* Bridge::instance() {
    if (!instance_) { instance_ = new Bridge(); }
    return instance_;
}
Bridge::Bridge() {
    Py_Initialize();
    _import_array();
    ...
}
void Bridge::foo() {
    ... // other stuff using PyArray functions
}

"main.cpp":

#include "Bridge.h"

int main(void)
{
    NDArray my_array(...);
    Bridge::instance()->exec(42, "hello", my_array); 
    return 0;
}

1 Answer 1

1

I've since learned that one problem is that the calls to the PyArray functions should happen in the same compilation unit as the call to import_array (the NumPy initialization function).

One way to solve this problem is to "wrap" the PyArray_* functions internally and use them rather than the NumPy API directly.

There may be another solution found here.

My solution:

Create a "numpy_wrappers.h" file:

...
#include "numpy/ndarraytypes.h"

int NumPyArray_NDIM(PyObject* obj);
npy_intp NumPyArray_DIM(PyObject* obj, int i);
void *NumPyArray_DATA(PyObject* obj);
...

Then implement these by "wrapping" the original functions in the same source file as your call to import_array (the NumPy initialization function):

...
Bridge::Bridge() {
    Py_Initialize();
    _import_array();
    ...
}
...
/// Wraps PyArray_NDIM
int NumPyArray_NDIM(PyObject* obj)
{
    return PyArray_NDIM((PyArrayObject*)obj);
}

/// Wraps PyArray_DIM
npy_intp NumPyArray_DIM(PyObject* obj, int i)
{
    return PyArray_DIM((PyArrayObject*)obj, i);
}

/// Wraps PyArray_DATA
void* NumPyArray_DATA(PyObject* obj)
{
    return PyArray_DATA((PyArrayObject*)obj);
}
...

Then they can be used in template headers, like so:

...
template <typename Y> bp::object convert(NDArray<Y> v)
{
... use NumPyArray_ functions to create and return a NumPy array
... No more segfaults!
}
...

You can see an in-depth implementation of this here, a toolbox for seamlessly converting between some C++ STL types and Python standard types.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.