Sending a C++ array to Python/NumPy and back I am going to send a C++ array to a Python function as NumPy array and get back another NumPy array. After consulting with NumPy documentation and some other threads and tweaking the code, the code is finally working but I would like to know if this code is written optimally considering the:


Unnecessary copying of the array between C++ and Numpy (Python).
Correct dereferencing of the variables.
Easy straight-forward approach.


C++ code:

// python_embed.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "Python.h"
#include "numpy/arrayobject.h"
#include&lt;iostream&gt;

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    Py_SetProgramName(argv[0]);
    Py_Initialize();
    import_array()

    // Build the 2D array
    PyObject *pArgs, *pReturn, *pModule, *pFunc;
    PyArrayObject *np_ret, *np_arg;
    const int SIZE{ 10 };
    npy_intp dims[2]{SIZE, SIZE};
    const int ND{ 2 };
    long double(*c_arr)[SIZE]{ new long double[SIZE][SIZE] };
    long double* c_out;
    for (int i{}; i &lt; SIZE; i++)
        for (int j{}; j &lt; SIZE; j++)
            c_arr[i][j] = i * SIZE + j;

    np_arg = reinterpret_cast&lt;PyArrayObject*&gt;(PyArray_SimpleNewFromData(ND, dims, NPY_LONGDOUBLE, 
        reinterpret_cast&lt;void*&gt;(c_arr)));

    // Calling array_tutorial from mymodule
    PyObject *pName = PyUnicode_FromString("mymodule");
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);
    if (!pModule){
        cout &lt;&lt; "mymodule can not be imported" &lt;&lt; endl;
        Py_DECREF(np_arg);
        delete[] c_arr;
        return 1;
    }
    pFunc = PyObject_GetAttrString(pModule, "array_tutorial");
    if (!pFunc || !PyCallable_Check(pFunc)){
        Py_DECREF(pModule);
        Py_XDECREF(pFunc);
        Py_DECREF(np_arg);
        delete[] c_arr;
        cout &lt;&lt; "array_tutorial is null or not callable" &lt;&lt; endl;
        return 1;
    }
    pArgs = PyTuple_New(1);
    PyTuple_SetItem(pArgs, 0, reinterpret_cast&lt;PyObject*&gt;(np_arg));
    pReturn = PyObject_CallObject(pFunc, pArgs);
    np_ret = reinterpret_cast&lt;PyArrayObject*&gt;(pReturn);
    if (PyArray_NDIM(np_ret) != ND - 1){ // row[0] is returned
        cout &lt;&lt; "Function returned with wrong dimension" &lt;&lt; endl;
        Py_DECREF(pFunc);
        Py_DECREF(pModule);
        Py_DECREF(np_arg);
        Py_DECREF(np_ret);
        delete[] c_arr;
        return 1;
    }
    int len{ PyArray_SHAPE(np_ret)[0] };
    c_out = reinterpret_cast&lt;long double*&gt;(PyArray_DATA(np_ret));
    cout &lt;&lt; "Printing output array" &lt;&lt; endl;
    for (int i{}; i &lt; len; i++)
        cout &lt;&lt; c_out[i] &lt;&lt; ' ';
    cout &lt;&lt; endl;

    // Finalizing
    Py_DECREF(pFunc);
    Py_DECREF(pModule);
    Py_DECREF(np_arg);
    Py_DECREF(np_ret);
    delete[] c_arr;
    Py_Finalize();
    return 0;
}


Python Code:

def array_tutorial(a):
    print("a.shape={}, a.dtype={}".format(a.shape, a.dtype))
    print(a)
    a *= 2
    return a[-1]
1. Review


It's not recommended to use using namespace std; — the problem is that this imports all of the identifiers from std, and some of these may shadow names from other modules that you need to use. See this question on Stack Overflow.
The code doesn't check for success/failure of many of the functions it calls. These can all fail:


new long double[SIZE][SIZE]
PyArray_SimpleNewFromData
PyUnicode_FromString
PyTuple_New
PyTuple_SetItem
PyObject_CallObject


(and maybe others that I didn't spot).
There's no Py_DECREF for pArgs.
It would be possible to avoid constructing the argument tuple, by using PyObject_CallFunctionObjArgs instead of PyObject_CallObject.
Error messages should be written to standard error (cerr), not standard output.
It's clearer to use the macros EXIT_SUCCESS and EXIT_FAILURE from &lt;cstdlib&gt; instead of 0 and 1.
The code calls PyArray_NDIM and PyArray_SHAPE on np_ret without checking to see if this object is in fact an array. Call PyArray_Check first.
Using value initialization for int variables seems perverse to me. With int i{} you have to remember that the default constructor gives the variable the value 0. With int i = 0 there's no need to remember (and the code is no less efficient: the compiled code doesn't actually create a temporary 0 object and then call the int assignment constructor).
This code doesn't provide as much information about errors as it could. In particular, errors inside Python cause Python to create an exception object containing information about the error (see "Exception Handling"). It would be a good idea to print this object if it exists, by checking PyErr_Occurred and then calling PyErr_Print.
Each block of error handling code has to undo the effect of all the previous successful blocks of code. This makes the length of the function quadratic in the number of error cases! This is risky, because every time you change something, you have to adjust all the error cases accordingly, and it is very easy to forget (as in §1.3 above). Also, the pain of writing out all the error-handling code makes it tempting to skip error handling for functions that you believe are likely to succeed (as in §1.2 above).

See the revised code below for one way to arrange that each "undo" operation appears just once.


2. Revised code

This is untested, so probably has some errors.

int _tmain(int argc, _TCHAR* argv[])
{
    int result = EXIT_FAILURE;

    Py_SetProgramName(argv[0]);
    Py_Initialize();
    import_array();

    // Build the 2D array in C++
    const int SIZE = 10;
    npy_intp dims[2]{SIZE, SIZE};
    const int ND = 2;
    long double(*c_arr)[SIZE]{ new long double[SIZE][SIZE] };
    if (!c_arr) {
        std::cerr &lt;&lt; "Out of memory." &lt;&lt; std::endl;
        goto fail_c_array;
    }
    for (int i = 0; i &lt; SIZE; i++)
        for (int j = 0; j &lt; SIZE; j++)
            c_arr[i][j] = i * SIZE + j;

    // Convert it to a NumPy array.
    PyObject *pArray = PyArray_SimpleNewFromData(
        ND, dims, NPY_LONGDOUBLE, reinterpret_cast&lt;void*&gt;(c_arr));
    if (!pArray)
        goto fail_np_array;
    PyArrayObject *np_arr = reinterpret_cast&lt;PyArrayObject*&gt;(pArray);

    // import mymodule.array_tutorial
    const char *module_name = "mymodule";
    PyObject *pName = PyUnicode_FromString(module_name);
    if (!pName)
        goto fail_name;
    PyObject *pModule = PyImport_Import(pName);
    Py_DECREF(pName);
    if (!pModule)
        goto fail_import;
    const char *func_name = "array_tutorial";
    PyObject *pFunc = PyObject_GetAttrString(pModule, func_name);
    if (!pFunc)
        goto fail_getattr;
    if (!PyCallable_Check(pFunc)){
        std::cerr &lt;&lt; module_name &lt;&lt; "." &lt;&lt; func_name
                  &lt;&lt; " is not callable." &lt;&lt; std::endl;
        goto fail_callable;
    }

    // np_ret = mymodule.array_tutorial(np_arr)
    PyObject *pReturn = PyObject_CallFunctionObjArgs(pFunc, pArray, NULL);
    if (!pReturn)
        goto fail_call;
    if (!PyArray_Check(pReturn)) {
        std::cerr &lt;&lt; module_name &lt;&lt; "." &lt;&lt; func_name
                  &lt;&lt; " did not return an array." &lt;&lt; std::endl;
        goto fail_array_check;
    }
    PyArrayObject *np_ret = reinterpret_cast&lt;PyArrayObject*&gt;(pReturn);
    if (PyArray_NDIM(np_ret) != ND - 1) {
        std::cerr &lt;&lt; module_name &lt;&lt; "." &lt;&lt; func_name
                  &lt;&lt; " returned array with wrong dimension." &lt;&lt; std::endl;
        goto fail_ndim;
    }

    // Convert back to C++ array and print.
    int len = PyArray_SHAPE(np_ret)[0];
    c_out = reinterpret_cast&lt;long double*&gt;(PyArray_DATA(np_ret));
    std::cout &lt;&lt; "Printing output array" &lt;&lt; std::endl;
    for (int i = 0; i &lt; len; i++)
        std::cout &lt;&lt; c_out[i] &lt;&lt; ' ';
    std::cout &lt;&lt; std::endl;
    result = EXIT_SUCCESS;

fail_ndim:
fail_array_check:
    Py_DECREF(pReturn);
fail_call:
fail_callable:
    Py_DECREF(pFunc);
fail_getattr:
    Py_DECREF(pModule);
fail_import:
fail_name:
    Py_DECREF(pArray);
fail_np_array:
    delete[] c_arr;
fail_c_array:
    if (PyErr_Check())
        PyErr_Print();  
    PyFinalize();
    return result;
}