Есть ли способ использовать pythonappend с новой встроенной функцией SWIG?
У меня есть небольшой проект, который прекрасно работает с SWIG. В частности, некоторые из моих функций возвращают std::vector
s, которые переводятся в кортежи в Python. Теперь я делаю много цифр, поэтому у меня просто SWIG конвертирует их в массивы numpy после того, как они возвращаются из кода С++. Для этого в SWIG я использую что-то вроде следующего.
%feature("pythonappend") My::Cool::Namespace::Data() const %{ if isinstance(val, tuple) : val = numpy.array(val) %}
(На самом деле существует несколько функций с именем Data, некоторые из которых возвращают float, поэтому я проверяю, что val
на самом деле является кортежем.) Это работает просто красиво.
Но я также хотел бы использовать флаг -builtin
, который теперь доступен. Звонки на эти функции данных редки и в основном интерактивны, поэтому их медленность не проблема, но есть и другие медленные циклы, которые значительно ускоряются со встроенной опцией.
Проблема заключается в том, что когда я использую этот флаг, функция pythonappend молча игнорируется. Теперь данные снова вернут кортеж. Есть ли способ вернуть массив numpy? Я пробовал использовать typemaps, но он превратился в гигантский беспорядок.
Изменить:
Бореалид ответил на вопрос очень красиво. Для полноты я включаю пару связанных, но очень тонких типов, которые мне нужны, потому что я возвращаюсь по ссылке const, и я использую векторы векторов (не начинайте!). Они достаточно разные, и я не хочу, чтобы кто-то еще спотыкался о том, чтобы выяснить незначительные отличия.
%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
Я согласен с вами в том, что использование typemap
становится немного грязным, но это правильный способ выполнить эту задачу. Вы также правы, что в документации SWIG прямо не сказано, что %pythonappend
несовместимо с -builtin
, но это сильно подразумевается: %pythonappend
добавляет к прокси-классу Python, а прокси-класс Python вообще не существует в сочетании с флагом -builtin
.
До того, как вы делали, SWIG конвертировал объекты С++ 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);
}
Это использует функции numpy уровня C для построения и возврата массива. Для этого он:
- Обеспечивает, что файл NumPy
arrayobject.h
включен в выходной файл С++
- Вызывается
import_array
для вызова при загрузке модуля Python (в противном случае все методы NumPy будут segfault)
- Отображает любые возвращаемые значения
std::vector<int>
в массивы NumPy с помощью typemap
Этот код должен быть помещен перед %import
заголовками, которые содержат функции, возвращающие std::vector<int>
. Помимо этого ограничения, он полностью автономный, поэтому он не должен добавлять слишком много субъективного "беспорядка" к вашей кодовой базе.
Если вам нужны другие типы векторов, вы можете просто изменить бит NPY_INT
и все int*
и int
, в противном случае дублируя вышеприведенную функцию.