如何用SWIG实现Numpy数组与自定义模板类的对接?
问题
我尝试用SWIG实现Python与C++代码的对接,遇到了转换函数未生效的问题,简化后的场景如下:
背景代码
fxp类型定义
template<int W, int I, bool S, ac_q_mode Q, ac_o_mode O, bool force_float> using fxp = std::conditional_t<force_float, double, ac_fixed<W, I, S, Q, O>>;
该类型借助AC types库处理定点数,通过force_float参数可强制转为double。
node模板类
#ifndef NODE_H #define NODE_H #include <deque> #include <algorithm> #include "ac_types.h" template<typename T> class node { public: node() {} ~node() {} T pop() { T sample = fifo.front(); fifo.pop_front(); return sample; } void push(const T &sample){ fifo.push_back(sample); data.push_back(sample); } bool empty() { return fifo.empty(); } size_t size() { return data.size(); } template<typename U> std::deque<T> get(); std::deque<double> get(){ std::deque<double> rval(data.size()); std::transform( data.cbegin(), data.cend(), rval.begin(), [](T arg) -> double { return (double) arg; }); return rval; } std::deque<T> fifo; std::deque<T> data; }; #endif
希望在Python中访问node类的fifo和data成员,但不想显式实例化所有定点类型模板。
当前SWIG配置
%begin %{ #define SWIG_PYTHON_CAST_MODE %} %ignore log; %ignore fx_cntxt; %module libdsp %{ #include "ac_types.h" #include "node.h" #include <deque> #include <numpy/ndarrayobject.h> %} %include "numpy.i" %include "std_deque.i" %include "std_vector.i" %include "std_string.i" // Initialize NumPy API %init %{ import_array(); %} // Generic typemap for std::deque<T> to NumPy array conversion %typemap(out) std::deque<T> { $result = deque_to_numpy($1); } // Generic typemap for NumPy array to std::deque<T> conversion %typemap(in) std::deque<T> (PyObject* obj) { $1 = numpy_to_deque<T>(obj); } namespace std { %template(vectori) vector<int>; %template(vectord) vector<double>; %template(dequei) deque<int>; %template(dequed) deque<double>; }; %include "ac_types.h" %include "node.h" %template(node1) node<fxp<16, 1, AC_RND, AC_SAT, false>>; %template(node2) node<fxp<8, 1, AC_RND, AC_SAT, true>>;
转换函数实现
template<typename T> PyObject* deque_to_numpy(const std::deque<T>& deque) { npy_intp size = deque.size(); PyObject* numpy_array = PyArray_SimpleNew(1, &size, NPY_DOUBLE); double* data = static_cast<double*>(PyArray_DATA((PyArrayObject*)numpy_array)); if (std::is_same<T, double>::value) { for (npy_intp i = 0; i < size; ++i) { data[i] = deque[i]; } } else { for (npy_intp i = 0; i < size; ++i) { data[i] = deque[i].to_double(); } } return numpy_array; } template<typename T> std::deque<T> numpy_to_deque(PyObject* numpy_array) { std::deque<T> deque; if (!PyArray_Check(numpy_array)) { throw std::invalid_argument("Input is not a valid NumPy array"); } PyArrayObject* array = (PyArrayObject*)numpy_array; npy_intp size = PyArray_SIZE(array); double* data = static_cast<double*>(PyArray_DATA(array)); if (std::is_same<T, double>::value) { for (npy_intp i = 0; i < size; ++i) { deque.push_back(data[i]); } } else { for (npy_intp i = 0; i < size; ++i) { deque.push_back(T(data[i])); } } return deque; }
问题现象
生成的SWIG包装代码中完全没有转换函数的痕迹,无法实现Python与node实例内容的对接。
解决方案
问题根源
- 泛型typemap未匹配具体化类型:SWIG的
std::deque<T>泛型typemap不会自动应用到std::deque<fxp<...>>这类具体化模板类型,需显式关联。 - 转换函数未暴露给SWIG:
deque_to_numpy和numpy_to_deque仅在C++中定义,SWIG无法识别这些函数,无法生成调用逻辑。 - 默认包装逻辑覆盖自定义typemap:
%include "std_deque.i"会生成std::deque的默认包装,可能覆盖自定义typemap。
修复步骤
1. 让SWIG识别并实例化转换函数
在SWIG接口文件的%{ ... %}块中添加转换函数的声明和显式实例化,确保SWIG能生成对应调用代码:
%{ // 声明转换函数 template<typename T> PyObject* deque_to_numpy(const std::deque<T>& deque); template<typename T> std::deque<T> numpy_to_deque(PyObject* numpy_array); // 实例化对应fxp和double类型的版本 template PyObject* deque_to_numpy(const std::deque<fxp<16, 1, AC_RND, AC_SAT, false>>&); template PyObject* deque_to_numpy(const std::deque<fxp<8, 1, AC_RND, AC_SAT, true>>&); template PyObject* deque_to_numpy(const std::deque<double>&); template std::deque<fxp<16, 1, AC_RND, AC_SAT, false>> numpy_to_deque(PyObject*); template std::deque<fxp<8, 1, AC_RND, AC_SAT, true>> numpy_to_deque(PyObject*); template std::deque<double> numpy_to_deque(PyObject*); %}
2. 调整typemap优先级
将自定义typemap放在%include "std_deque.i"之前,或者先清除默认typemap再定义自己的:
// 清除std::deque的默认out/in typemap %clear std::deque<T>; %typemap(out) std::deque<T> { $result = deque_to_numpy($1); } %clear std::deque<T>; %typemap(in) std::deque<T> (PyObject* obj) { $1 = numpy_to_deque<T>(obj); } // 之后再包含std_deque.i %include "std_deque.i"
3. 为node成员变量单独设置typemap
如果泛型typemap不生效,可直接针对node<T>的成员变量定义typemap:
%typemap(out) node<T>::fifo, node<T>::data { $result = deque_to_numpy($1); } %typemap(in) node<T>::fifo, node<T>::data (PyObject* obj) { $1 = numpy_to_deque<T>(obj); }
4. 简化模板实例化(可选)
若不想手动实例化所有fxp类型,可通过宏批量生成:
#define DEFINE_NODE(W, I, S, Q, O, FF) \ %template(node_##W##_##I##_##S##_##Q##_##O##_##FF) node<fxp<W, I, S, Q, O, FF>>; // 调用宏生成需要的实例 DEFINE_NODE(16, 1, AC_RND, AC_SAT, false); DEFINE_NODE(8, 1, AC_RND, AC_SAT, true);
验证要点
- 检查生成的
libdsp_wrap.cxx文件,确认是否存在调用deque_to_numpy和numpy_to_deque的代码。 - 在Python中测试访问
node.fifo/node.data是否返回NumPy数组,以及赋值操作是否正常。
内容的提问来源于stack exchange,提问作者tristan_dh
相关产品推荐
相关产品推荐

