有没有办法使用SWIG新内置特性中的pythonappend?

77 投票
1 回答
2143 浏览
提问于 2025-04-17 12:59

我有一个小项目,使用SWIG运行得非常顺利。特别是,我的一些函数返回的是std::vector,在Python中会被转换成元组。因为我做很多数字计算,所以我让SWIG在从C++代码返回这些数据后,把它们转换成numpy数组。为此,我在SWIG中使用了类似下面的代码。

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

(实际上,有几个叫做Data的函数,其中一些返回浮点数,所以我需要检查val确实是一个元组。)这样做效果很好。

不过,我也想使用现在可用的-builtin选项。调用这些Data函数的情况比较少,而且大多是交互式的,所以它们的慢并不是问题,但有其他一些慢的循环在使用builtin选项后会显著加速。

问题是,当我使用这个选项时,pythonappend功能就被默默忽略了。现在,Data又只返回一个元组了。我有没有办法仍然返回numpy数组呢?我尝试使用typemaps,但结果变得一团糟。

编辑:

Borealid很不错地回答了这个问题。为了完整起见,我还包括了一些相关但稍有不同的typemaps,因为我通过常量引用返回,并且使用了向量的向量(别开始讨论!)。这些差异足够大,我不想让其他人费力去弄清楚这些小区别。

%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的方法解决(在这里解释)。

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 文件
  • 在加载 Python 模块时调用 import_array(否则,所有 NumPy 方法会崩溃)
  • 使用 typemap 将任何返回的 std::vector<int> 映射到 NumPy 数组

这段代码应该放在你 %import 返回 std::vector<int> 的函数头文件之前。除此之外,它是完全自包含的,所以不会给你的代码库增加太多主观上的“麻烦”。

如果你需要其他类型的向量,只需更改 NPY_INT 以及所有的 int*int 部分,或者直接复制上面的函数。

撰写回答