我将C ++数组作为NumPy数组发送到Python函数,并返回另一个NumPy数组。在咨询了NumPy文档和一些其他线程并调整了代码之后,代码终于可以正常工作了,但是我想知道是否考虑到以下情况最佳地编写了此代码:


不必要的复制C ++和Numpy(Python)之间的数组。
正确的变量引用。
简单直接的方法。

C ++代码:

// 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<iostream>

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 < SIZE; i++)
        for (int j{}; j < SIZE; j++)
            c_arr[i][j] = i * SIZE + j;

    np_arg = reinterpret_cast<PyArrayObject*>(PyArray_SimpleNewFromData(ND, dims, NPY_LONGDOUBLE, 
        reinterpret_cast<void*>(c_arr)));

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

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


Python代码:

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


评论

如果答案之一值得额外赏金,请奖励该赏金。

为了测试代码,发布Python代码(array_tutorial.py)很有用!

@TotteKarlsson,完成了!

#1 楼

1.回顾


不建议使用using namespace std;-问题是这会从std导入所有标识符,其中一些可能会阴影掉其他需要使用的模块的名称。请参阅有关堆栈溢出的问题。

代码不会检查它调用的许多函数的成功/失败。这些都可能失败:


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

(也许还有其他我没发现的)。

Py_DECREF没有pArgs
通过使用PyObject_CallFunctionObjArgs而不是PyObject_CallObject可以避免构造参数元组。
错误消息应该写为标准错误(cerr),而不是标准输出。在EXIT_SUCCESS上而不检查该对象是否实际上是数组。首先调用EXIT_FAILURE
<cstdlib>变量使用值初始化对我来说似乎很不对劲。使用0时,您必须记住默认构造函数将变量的值设置为0。使用1时,无需记住(并且代码的效率同样低:编译后的代码实际上不会创建临时0对象,然后调用PyArray_NDIM赋值构造函数)。
此代码没有提供尽可能多的有关错误的信息。特别是,Python内部的错误会导致Python创建一个包含有关错误信息的异常对象(请参见“异常处理”)。最好打印此对象(如果存在),方法是先检查PyArray_SHAPE,然后调用np_ret

错误处理代码的每个块都必须撤消所有先前成功的代码块的影响。在错误情况下,这使函数的长度是平方的!这是有风险的,因为每次更改某项时,都必须相应地调整所有错误情况,并且很容易忘记(如以上第1.3节所述)。同样,写出所有错误处理代码的痛苦使您很容易跳过对您认为可能会成功的函数的错误处理(如上述第1.2节所述)。

请参见下面的修订代码一种安排每个“撤消”操作只出现一次的方法。


2。修改后的代码

未经测试,可能有一些错误。

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 << "Out of memory." << std::endl;
        goto fail_c_array;
    }
    for (int i = 0; i < SIZE; i++)
        for (int j = 0; j < 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<void*>(c_arr));
    if (!pArray)
        goto fail_np_array;
    PyArrayObject *np_arr = reinterpret_cast<PyArrayObject*>(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 << module_name << "." << func_name
                  << " is not callable." << 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 << module_name << "." << func_name
                  << " did not return an array." << std::endl;
        goto fail_array_check;
    }
    PyArrayObject *np_ret = reinterpret_cast<PyArrayObject*>(pReturn);
    if (PyArray_NDIM(np_ret) != ND - 1) {
        std::cerr << module_name << "." << func_name
                  << " returned array with wrong dimension." << std::endl;
        goto fail_ndim;
    }

    // Convert back to C++ array and print.
    int len = PyArray_SHAPE(np_ret)[0];
    c_out = reinterpret_cast<long double*>(PyArray_DATA(np_ret));
    std::cout << "Printing output array" << std::endl;
    for (int i = 0; i < len; i++)
        std::cout << c_out[i] << ' ';
    std::cout << 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;
}


评论


\ $ \ begingroup \ $
我不是C ++专家,但是_tmain和_TCHAR不应该是main和char吗?
\ $ \ endgroup \ $
– Ethan Bierlein
2015年6月13日19:15在

\ $ \ begingroup \ $
@Ethan:这些是Microsoft扩展(请参阅此处)。 Py_SetProgramName在Python 2中使用char *,在Python 3中使用wchar_t *,因此可能使用_tmain可以实现某种可移植性,但是我不知道,我只是从OP复制了它们。似乎有足够的评论意见,无需赘述。
\ $ \ endgroup \ $
–加雷斯·里斯(Gareth Rees)
2015年6月13日19:30



\ $ \ begingroup \ $
您不应该打印/访问所有尺寸吗?你只在这里打印
\ $ \ endgroup \ $
–简单名称
20年8月7日在18:49

\ $ \ begingroup \ $
@simplename:请参阅原始帖子。
\ $ \ endgroup \ $
–加雷斯·里斯(Gareth Rees)
20年8月7日在18:53

\ $ \ begingroup \ $
足够公平:)
\ $ \ endgroup \ $
–简单名称
20年8月7日在18:56

#2 楼

我发现这个简单的代码很好用,尽管它可以将数组作为元组发送给Python函数,而不是numpy数组,但是在Python中将元组更改为numpy数组更容易:

c++
...
PyObject* pArgs = PyTuple_New(2);
PyTuple_SetItem(pArgs, 0, Py_BuildValue("i", 1));
PyTuple_SetItem(pArgs, 1, Py_BuildValue("i", 2));
PyObject_CallObject(pFunc, pArgs);
...




python
def func(*list):
  print list[0] + list[1]


评论


\ $ \ begingroup \ $
这种方法比较慢(并且占用大量内存),因为它必须为数组中的每个元素调用Py_BuildValue,而本文中的代码将C ++数组转换为NumPy数组,而无需复制或转换。 (此外,PyTuple_New和Py_BuildValue可能会失败,因此检查结果是个好主意。)
\ $ \ endgroup \ $
–加雷斯·里斯(Gareth Rees)
17年4月15日在12:33