用 Boost.Python 写扩展库(2 类和结构体)

来源:互联网 发布:java算法基础 编辑:程序博客网 时间:2024/06/15 09:38

用 Boost.Python 写扩展库(2 类和结构体)

在 C++ 中,类和结构体本质上是一样的,唯一的区别是,类的成员默认都是 private 的,而结构体的成员默认都是 public 的。因此这里只讲类的导出方法即可。

2.1  包装简单类

当我需要导出 C++ 类给 Python 时,比如我需要导出的类的声明如下


1  class Complex2  {3  public:4      double real;5      double imag;6      Complex(double rp, double ip);7      double GetArg() const;8  };

我可以使用如下胶水代码来包装 
1  class_<Complex>("Complex", init<double, double>())2  .def_readwrite("real", &Complex::real)3  .def_readwrite("imag", &Complex::imag)4  .def("GetArg", &Complex::GetArg)5  ;

这段胶水代码的意思是,先构造一个临时对象,该对象的类型是 init<double, double> (模板类 init 的一个实例),然后用字符串 "Complex" 和 这个临时对象构造另一个临时对象,该对象的类型是 class_<Complex> (模板类 class_ 的一个实例)。然后调用第二个临时对象的 def_readwrite 方法,该方法返回这个对象本身,因此可以接着再调用这个对象的 def_readwrite 方法和 def 方法。

下面是一个完整的例子,这个例子中,为了更容易写注释,我没有使用临时对象,而是给对象取了个名字叫 pyComplex,这样更方便一些:


 1  /* 2  filename: Complex.cpp 3   */ 4  #include <cmath> 5  #include <boost/python.hpp>// 包含 Boost.Python 的头文件 6 7  class Complex              // 复数类 8  { 9  public:10      double real;           // 表示实部的成员11      double imag;           // 表示虚部的成员1213      // 构造函数,以及初始化列表14      Complex(double rp, double ip):15          real(rp),          // 初始化实部16          imag(ip)           // 初始化虚部17      {18      }1920      // 获取复数的幅角21      double GetArg() const22      {23          return atan2(imag, real);24      }2526  };2728  using namespace boost::python; // 引入名字空间2930  BOOST_PYTHON_MODULE(ADT) // 胶水代码入口,导出一个名为“ADT”的模块31  {32      // 构造一个类型为 "boost::python::class_<Complex>" 的对象 pyComplex33      // 构造参数为字符串 "Complex"34      // 表示要将 C++ 类 Complex 导出到 Python 中去,名字也叫 "Complex"35      class_<Complex> pyComplex("Complex", no_init);3637      // 导出它的构造方法,声明它的构造方法有两个 double 类型的参数38      pyComplex.def(init<double, double>());3940      // 导出它的公有成员 real,41      // 该成员在 Complex 类中的位置是 &Complex::real42      // 导出到 Python 中之后的名字也是 "real"43      pyComplex.def_readwrite("real", &Complex::real);4445      // 导出它的公有成员 imag,46      // 该成员在 Complex 类中的位置是 &Complex::imag47      // 导出到 Python 中之后的名字也是 "imag"48      pyComplex.def_readwrite("imag", &Complex::imag);4950      // 导出它的成员方法 GetArg51      // 该方法在 Complex 类中的入口是 &Complex::GetArg52      // 导出到 Python 中之后的名字也是 "GetArg"53      pyComplex.def("GetArg", &Complex::GetArg);54  }

用上一章讲的步骤编译这个程序,生成动态连接库 ADT.so (Linux 下) 或 ADT.dll (Windows 下)。然后我可以执行一段 Python 脚本来验证一下:
>>> import ADT>>> z = ADT.Complex(-1, 1.5)>>> print z.real, '+', str(z.imag) + 'i'-1.0 + 1.5i>>> z.imag = 0>>> print z.real, '+', str(z.imag) + 'i'-1.0 + 0.0i>>> print z.GetArg()3.14159265359

这样,我就导出了一个 C++ 的复数类给 Python,同时导出了该复数类的构造方法、成员方法、公有成员变量。

我还可以包装一个公有成员变量,使它在 Python 中只能读不能写。这只需要我改用

boost::python::class_<Complex>:def_readonly()  // 用于包装只读的公有成员变量

即可。

下面我们看看如何导出更多的内容。

2.2  特殊方法和运算符重载

Python 的对象有一堆特殊方法,相比于一般方法,这些方法有其特别的含义。比如在 Python 语言中语句 abs(obj) 将返回调用 obj.__abs__() 方法的结果。语句 len(obj) 将返回调用 obj.__len__() 方法的结果。语句 print obj 将在屏幕上打印 obj.__repr__() 的返回值。因此,当我导出 C++ 类的成员方法时,若导出的方法名为“__XXX__”时,我需要特别注意,这些方法在 Python 中有特别的含义,因为它们将被 Python 中特定内置函数调用的。这里“__XXX__”可能的形式和会调用它们的内置函数分别是

 Table 1:  内置函数及其调用的特殊方法

方法名内置函数含义方法名内置函数含义__int__int()转换为 int 数据__bool__bool()转换为 bool 数据__long__long()转换为 long 数据__float__float()转换为 float 数据__complex__complex()转换为 complex 数据__str__str()转换为字符串数据__abs__abs()求绝对值__len__len()求长度__pow__pow()最为底数求幂__rpow__rpow()作为指数求密__hex__hex()转换为十六进制字符串__oct__oct()转换为八进制字符串__repr__repr()转换为可打印字符串    

例如,我可以给 Complex 类增加一些这样的特殊方法:


 1  class Complex 2  { 3  public: 4      double real; 5      double imag; 6      Complex(double rp, double ip); 7      double GetArg() const; 8 910      std::string ToString() const;   // 新增的转换成字符串的方法11      operator double() const;        // 新增的类型转换操作符12      double abs() const;             // 新增的方法13  };

我可以使用如下代码来包装 
1  class_<Complex>("Complex", init<double, double>())2  .def_readwrite("real", &Complex::real)3  .def_readwrite("imag", &Complex::imag)4  .def("GetArg", &Complex::GetArg)5  .def("__repr__", &Complex::ToString)         // 转换成可读字符串6  .def("__float__", &Complex::operator double) // 转换到为 float7  .def("__abs__", &Complex::abs)               // 求绝对值8  ;

为了方便起见,同时为了强调这几个方法的特殊性,Boost.Python 还提供了更简洁的写法用来包装特殊方法。我可以改写上面的胶水代码为 
1  class_<Complex>("Complex", init<double, double>())2  .def_readwrite("real", &Complex::real)3  .def_readwrite("imag", &Complex::imag)4  .def("GetArg", &Complex::GetArg)5  .def("__repr__", &Complex::ToString) // 转换成可读字符串6  .def(float_(self)) // 转换到为 float7  .def(abs(self))    // 求绝对值8  ;

待会儿讲完操作符重载后,我会给出一个完整的例子。

其实,上面的“__XXX__”还可能有更多的形式,并且拥有更特殊的含义,即表示运算符重载。 C++ 和 Python 都支持运算符重载,不过 Python 中的运算符重载采用了更简洁的写法。比如,在 C++ 中,我要重载 Complex 类的加法运算符,我需要添加方法

Complex & Complex::operator + (Complex &L);

或者添加函数

Complex & operator + (Complex &R, Complex &L);

而在 Python 中,我只需要添加方法

class Complex:    def __add__(self, L):        # ...

如果我希望包装的 C++ 类支持运算符重载的话,我可以将相应的方法包装成 Python 中对应的方法。如果我给 Complex 类添加了加法运算:

class Complex{    ...    Complex &operator + (const Complex &L) const;    ...};

我可以用如下胶水代码来包装:

class_<Complex>("Complex", init<double, double>()).def("__add__", &Complex::operator +);

对于这些特殊的运算符重载方法,我还可以有更方便的写法来代替上面的胶水代码:

class_<Complex>("Complex", init<double, double>()).def(self + self);

有了上面介绍的工具,现在我可以给我的 Complex 类添加一坨方法重载各种常用运算符,让它用起来更方便。完整的例子如下:


  1  /*  2  filename: MoreComplex.cpp  3   */  4  #include <cmath>  5  #include <cstdio>  6  #include <string>  7  #include <boost/python.hpp>// 包含 Boost.Python 的头文件  8  9  // 复数类 10  class Complex 11  { 12  public: 13      double real;    // 实部 14      double imag;    // 虚部 15 16      Complex(double rp, double ip); // 构造方法 17      Complex(const Complex &c);     // 拷贝构造方法 18 19      double GetArg() const;         // 获取复数的幅角 20 21      std::string ToString() const;  // 转换成可读字符串 22      operator double() const;       // 到 double 类型的隐式转换方法 23      double abs() const;            // 求模 24 25      // 复数和复数以及复数和浮点数的加法、减法和乘法 26      Complex operator + (const Complex &L) const; 27      Complex operator - (const Complex &L) const; 28      Complex operator + (double L) const; 29      Complex operator - (double L) const; 30      Complex operator * (const Complex &L) const; 31      Complex operator * (double L) const; 32  }; 33 34  using namespace boost::python; // 引入名字空间 35 36  BOOST_PYTHON_MODULE(ADT)       // 胶水代码入口,导出一个名为“ADT”的模块 37  { 38      // 包装 Complex 类 39      class_<Complex>("Complex", init<double, double>()) 40 41      // 包装另外一个构造函数 42      .def(init<const Complex &>()) 43 44      // 包装公有成员 45      .def_readwrite("real", &Complex::real) 46      .def_readwrite("imag", &Complex::imag) 47 48      // 包装成员方法 49      .def("GetArg", &Complex::GetArg) 50 51      // 包装特殊方法 52      .def("\_\_repr\_\_", &Complex::ToString) 53 54      // 包装运算符重载 55      .def(float_(self)) 56      .def(abs(self)) 57      .def(self + self) 58      .def(self + other<double>()) 59      .def(self - self) 60      .def(self - other<double>()) 61      .def(self * self) 62      .def(self * other<double>()) 63      ; 64  } 65 66  // 下面是 Complex 类的各个成员方法的实现 67 68  Complex::Complex(double rp, double ip): 69      real(rp), imag(ip) 70  { 71  } 72 73  Complex::Complex(const Complex &c): 74      real(c.real), imag(c.imag) 75  { 76  } 77 78  double Complex::GetArg() const 79  { 80      return atan2(imag, real); 81  } 82 83  std::string Complex::ToString() const 84  { 85      char buf[100]; 86      if (imag ==  0) 87          sprintf(buf, "%f", real); 88      else 89          sprintf(buf, "%f + %fi", real, imag); 90 91      return std::string(buf); 92  } 93 94  Complex::operator double() const 95  { 96      return abs(); 97  } 98 99  double Complex::abs() const100  {101      return sqrt(real*real + imag*imag);102  }103104  Complex Complex::operator + (const Complex &L) const105  {106      return Complex(this->real + L.real, this->imag + L.imag);107  }108109  Complex Complex::operator - (const Complex &L) const110  {111      return Complex(this->real - L.real, this->imag - L.imag);112  }113114  Complex Complex::operator + (double L) const115  {116      return Complex(this->real + L, this->imag);117  }118119  Complex Complex::operator - (double L) const120  {121      return Complex(this->real - L, this->imag);122  }123124  Complex Complex::operator * (const Complex &L) const125  {126      return Complex(real*L.real - imag*L.imag, real*L.imag + imag*L.real);127  }128129  Complex Complex::operator * (double L) const130  {131      return Complex(this->real * L, this->imag * L);132  }

用上一章讲的步骤编译这个程序,生成动态连接库 ADT.so (Linux 下) 或 ADT.dll (Windows 下)。然后我们写一个 Python 语言脚本来看一下我的 Complex 类工作得怎么样: 
 1  #!/usr/bin/env python 2  # filename: test.py 3  import ADT 4  a = ADT.Complex(3, 4) 5  b = ADT.Complex(6, 9) 6  c = ADT.Complex(-1, 0) 7  d = ADT.Complex(c) 8  print "a =", a 9  print "b =", b10  print "c =", c11  print "d =", d12  print "a + b =", a + b13  print "a + 1 =", a + 114  print "a * b =", a * b15  print "a * 2 =", a * 216  print "b - a =", b - a17  print "b - 3 =", b - 318  print "|a + b| =", abs(a + b)19  print "float(a) =", float(a)20  print "Arg(d) =", d.GetArg()

然后我们运行这个脚本,正确情况下我们可以看到输出
a = 3.000000 + 4.000000ib = 6.000000 + 9.000000ic = -1.000000d = -1.000000a + b = 9.000000 + 13.000000ia + 1 = 4.000000 + 4.000000ia * b = -18.000000 + 51.000000ia * 2 = 6.000000 + 8.000000ib - a = 3.000000 + 5.000000ib - 3 = 3.000000 + 9.000000i|a + b| = 15float(a) = 5.0Arg(d) = 3.14159265359

搞定!

2.3  继承

假如我写了两个 C++ 类,

class Base{public:    void foo();};class Derived{public:    void bar();};

如果我们错误地用如下的胶水代码包装这两个类,

class_<Base>("Base").def("foo", &Base::foo);class_<Derived>("Derived").def("bar", &Derived::bar);

那么在 Python 中我们调用 Derived 的 foo 方法会导致错误。因为, Python 并不知道 Derived 是 Base 的子类,从那里继承了一个 foo 方法。我们需要这样写:

class_<Derived, base<Base> >("Derived").def("bar", &Derived::bar);

即需要使用类模板 base<Base> 作为 class_ 的第二个模板参数即可。

2.4  总结

包装一个类,我们写的胶水代码中主要用到了如下几个类和类模板:

  1. boost::python::class_<>和 boost::python::init<>

    • class_<>用来包装类;

    • class_<>::def() 方法用来包装类的方法;

    • class_<>::def_readwrite() 方法用来包装类的成员;

    • class_<>::def_readonly() 方法用来包装类的成员,并使它在 Python 中只读;

    • init<X, Y, Z> 表示类的一个构造方法接受的三个参数,分别是 X,Y,Z 类型;

  2. boost::python::self 和 boost::python::other<>

    • self 表示重载的运算符中,操作数之一是对象本身

    • other<T> 表示重载的运算符中,操作数之一是 T 类型的对象

  3. boost::python::base<>, 举例:class_<X, base<Y> > 表示我们要包装的类 X 继承了 类Y。



原创粉丝点击