有没有办法在SWIG的新内置功能中使用pythonappend?

77

我有一个使用SWIG非常成功的小项目。特别是,我的一些函数返回std::vector,在Python中会被转换成元组(tuples)。由于我的项目需要进行很多数值计算,所以在从C++代码返回数据后,我直接让SWIG将其转换成numpy数组。为了做到这一点,在SWIG中我使用类似以下的东西。

%feature("pythonappend") My::Cool::Namespace::Data() const %{ if isinstance(val, tuple) : val = numpy.array(val) %}

(实际上,有几个名为Data的函数,其中一些返回浮点数,这就是为什么我要检查val是否真的是一个元组的原因。) 这很好地解决了问题。

但是,我也想使用现在可用的-builtin标志。对这些Data函数的调用很少,并且大多数是交互式的,因此它们的慢速不是问题,但是还有其他缓慢的循环,内置选项可以显著加快速度。

问题是当我使用该标志时,pythonappend功能会被默默忽略。现在,Data再次只返回一个元组。我是否有任何方法仍然可以返回numpy数组?我尝试使用typemaps,但变成了一个混乱。

编辑:

Borealid非常好地回答了这个问题。为了完整起见,我包括了一些相关但微妙不同的typemap,因为我通过const引用返回并使用vector的向量(不要开始!)。 这些差异足以使任何人在尝试弄清楚细微差别时都不会感到困难。

%typemap(out) std::vector<int>& {
  npy_intp result_size = $1->size();
  npy_intp dims[1] = { result_size };
  PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_INT);
  int* dat = (int*) PyArray_DATA(npy_arr);
  for (size_t i = 0; i < result_size; ++i) { dat[i] = (*$1)[i]; }
  $result = PyArray_Return(npy_arr);
}
%typemap(out) std::vector<std::vector<int> >& {
  npy_intp result_size = $1->size();
  npy_intp result_size2 = (result_size>0 ? (*$1)[0].size() : 0);
  npy_intp dims[2] = { result_size, result_size2 };
  PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_INT);
  int* dat = (int*) PyArray_DATA(npy_arr);
  for (size_t i = 0; i < result_size; ++i) { for (size_t j = 0; j < result_size2; ++j) { dat[i*result_size2+j] = (*$1)[i][j]; } }
  $result = PyArray_Return(npy_arr);
}

编辑2:

虽然不完全是我所寻求的,但类似的问题也可以使用@ MONK的方法进行解决(在此处解释)。


4
我认为您需要编写一个类型映射并在 C 端进行操作,才能完成此操作,因为 -builtin 会删除通常放置 pythonappend 的代码。您确定 -builtin 更快吗?(即,您进行了性能分析后决定使用它吗?)我会建议使用两个模块,一个带有 -builtin,一个没有。 - Flexo
我很惊讶没有警告 -builtin 会忽略 pythonappend。我无法应对将 std::vector 映射到 numpy 数组的挑战。我进行了分析,它显著加速了我的接口中最烦人的循环(不够长,不能休息;太长而无法频繁等待)。但我也意识到我可以将这个循环移到我的 C++ 代码中,尽管有点笨拙。所以我会这样做。不过,你的“两个模块”的建议很有意思,在其他情况下可能也很有用。 - Mike
你是否使用了 -Wall 参数调用 SWIG?我认为在这种情况下它会发出警告。 - Flexo
即使使用了-Wall选项,也没有警告(虽然这是一个非常大的忽略,我认为甚至不需要这个选项)。 - Mike
尝试使用Cython包装Data方法? - Paul Price
1个回答

8
我同意您的观点,使用typemap会有点混乱,但这是完成此任务的正确方法。您也说得对,SWIG文档并没有直接说明%pythonappend-builtin不兼容,但它强烈暗示了:%pythonappend添加到Python代理类中,而在使用-builtin标志时,Python代理类根本不存在。
在此之前,您所做的是让SWIG将C++ std::vector对象转换为Python元组,然后将这些元组传递回numpy,再次进行转换。
您真正想要做的是在C级别上仅转换一次。
以下是一些代码,可以将所有std::vector<int>对象转换为NumPy整数数组:
%{
#include "numpy/arrayobject.h"
%}

%init %{
    import_array();
%}

%typemap(out) std::vector<int> {
    npy_intp result_size = $1.size();

    npy_intp dims[1] = { result_size };

    PyArrayObject* npy_arr = (PyArrayObject*)PyArray_SimpleNew(1, dims, NPY_INT);
    int* dat = (int*) PyArray_DATA(npy_arr);

    for (size_t i = 0; i < result_size; ++i) {
        dat[i] = $1[i];
    }

    $result = PyArray_Return(npy_arr);
}

这里使用了C语言级别的numpy函数来构建和返回一个数组。按顺序,它会:
  • 确保在C++输出文件中包含了NumPy的arrayobject.h文件
  • 导致import_array在Python模块加载时调用(否则,所有NumPy方法都将崩溃)
  • 通过typemap将任何std::vector<int>返回映射为NumPy数组

这段代码应该放在你包含返回std::vector<int>的函数头文件之前。除此之外,它完全自包含,因此不应在代码库中增加太多主观的“杂乱”。

如果需要其他向量类型,则可以更改NPY_INT和所有的int*int位,以重复上述函数。


太棒了!我拥有你在typemap中的所有元素,但我还没有完全正确地将它们组合起来。虽然我还没有正式将其与我的项目一起使用,但我已经通过构建一个更简单的模块进行了相当彻底的测试。非常感谢! - Mike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接