如何通过SWIG在Python中扩展模板C++类以支持[]操作符

6 投票
3 回答
3212 浏览
提问于 2025-04-18 00:29

我有一个使用标准向量类的模板化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 个回答

0

为了提供一个更通用的解决方案:

%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指令,这是其他答案中没有提到的。

3

你可以分别扩展每种类型,就像这样:

%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)
7

你可以扩展整个模板,而不需要指定特定的类型。比如,你可以把代码修改成这样:

%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++的异常、崩溃或其他未定义行为。

撰写回答