如何通过SWIG在Python中扩展模板C++类以支持[]操作符
我有一个使用标准向量类的模板化C++数组类:
#include <vector>
#include <string>
using namespace std;
template<typename T>
class Array1D{
private:
vector<T> data_;
int xsize_;
public:
Array1D(): xsize_(0) {};
// creates vector of size nx and sets each element to t
Array1D(const int& nx, const T& t): xsize_(nx) {
data_.resize(xsize_, t);
}
T& operator()(int i) {return data_[i];}
T& operator[](int i) {return data_[i];}
};
我的SWIG接口文件看起来像这样:
%module test
%{
#define SWIG_FILE_WITH_INIT
#include "test.h"
%}
%include "std_vector.i"
// Array 1D Typemaps
// typemaps for standard vector<double>
namespace std{
%template(DoubleVector) vector<double>;
%template(IntVector) vector<int>;
}
%include "test.h"
%template(intArray1D) Array1D<int>;
%template(doubleArray1D) Array1D<double>;
%rename(__getitem__) operator[];
%extend Array1D<T>{
T& __getitem__(int i) {
return (*self)[i];
}
}
在创建模块后,当我在Python中创建一个Array1D,并输入a[2]时,我遇到了以下错误:
TypeError: 'doubleArray1D' object does not support indexing
我猜可能是我的接口文件中的扩展部分出了问题。我觉得它没有识别出类型T。有没有人有什么想法可以让我解决这个问题?
提前谢谢大家!
3 个回答
为了提供一个更通用的解决方案:
%define ArrayExtendVal(name, T)
%extend name {
T getitem(int i) {
return (*self)[i];
}
}
%enddef
%define ArrayExtendRef(name, T)
%extend name {
T& getitem(int i) {
return (*self)[i];
}
}
%enddef
%define ArrayExtendConstRef(name, T)
%extend name {
const T& getitem(int i) {
return (*self)[i];
}
}
%enddef
...
%ignore myNamespace::myClass::operator[]
%include "my_class.h"
ArrayExtendVal(myClass, double);
ArrayExtendRef(myClass, double);
ArrayExtendConstRef(myClass, double);
注意这里有一个%ignore
指令,这是其他答案中没有提到的。
你可以分别扩展每种类型,就像这样:
%extend doubleArray1D {
需要注意的是,这种扩展是虚拟的,意思是它只是告诉SWIG生成一些额外函数的代码,这些函数会成为导出的类的一部分。但是,这些函数只能访问你C++类的公共接口。
如果你有很多模板实例,你可以定义并使用一个SWIG宏:
%define ArrayExtend(name, T)
%extend name<T> {
T& __getitem__(int i) {
return (*self)[i];
}
}
%enddef
ArrayExtend(Array1D, double)
ArrayExtend(Array1D, int)
你可以扩展整个模板,而不需要指定特定的类型。比如,你可以把代码修改成这样:
%module test
%{
#include <vector>
%}
%inline %{
template<typename T>
class Array1D{
private:
std::vector<T> data_;
size_t xsize_;
public:
Array1D(): xsize_(0) {};
// creates vector of size nx and sets each element to t
Array1D(const size_t& nx, const T& t): xsize_(nx) {
data_.resize(xsize_, t);
}
T& operator[](const size_t i) {return data_.at(i);}
};
%}
%extend Array1D {
T __getitem__(size_t i) {
return (*$self)[i];
}
}
%template(intArray1D) Array1D<int>;
%template(doubleArray1D) Array1D<double>;
这样做的效果正如你所期待的,因为SWIG在生成包装代码时,会自动展开并填充
In [1]: import test
In [2]: a=test.intArray1D(10,1)
In [3]: a[0]
Out[3]: 1
In [4]: a[10]
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check
zsh: abort ipython
注意:我把.at()
而不是[]
,因为前者在索引无效时会抛出错误,而后者可能会导致未定义的行为。实际上,你可以利用SWIG的默认异常库,轻松地处理这些异常,做一些“智能”的操作:
%module test
%{
#include <vector>
%}
%include <std_except.i>
%inline %{
template<typename T>
class Array1D{
private:
std::vector<T> data_;
size_t xsize_;
public:
Array1D(): xsize_(0) {};
// creates vector of size nx and sets each element to t
Array1D(const size_t& nx, const T& t): xsize_(nx) {
data_.resize(xsize_, t);
}
T& operator[](const size_t i) {return data_.at(i);}
};
%}
%extend Array1D {
T __getitem__(size_t i) throw(std::out_of_range) {
return (*$self)[i];
}
}
%template(intArray1D) Array1D<int>;
%template(doubleArray1D) Array1D<double>;
只需这两行修改,就能让Python抛出IndexError
,而不是C++的异常、崩溃或其他未定义行为。