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

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

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

2.1  包装简单类

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


1  class Complex
2  {
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;           // 表示虚部的成员
12
13      // 构造函数,以及初始化列表
14      Complex(double rp, double ip):
15          real(rp),          // 初始化实部
16          imag(ip)           // 初始化虚部
17      {
18      }
19
20      // 获取复数的幅角
21      double GetArg() const
22      {
23          return atan2(imag, real);
24      }
25
26  };
27
28  using namespace boost::python; // 引入名字空间
29
30  BOOST_PYTHON_MODULE(ADT) // 胶水代码入口,导出一个名为“ADT”的模块
31  {
32      // 构造一个类型为 "boost::python::class_<Complex>" 的对象 pyComplex
33      // 构造参数为字符串 "Complex"
34      // 表示要将 C++ 类 Complex 导出到 Python 中去,名字也叫 "Complex"
35      class_<Complex> pyComplex("Complex", no_init);
36
37      // 导出它的构造方法,声明它的构造方法有两个 double 类型的参数
38      pyComplex.def(init<double, double>());
39
40      // 导出它的公有成员 real,
41      // 该成员在 Complex 类中的位置是 &Complex::real
42      // 导出到 Python 中之后的名字也是 "real"
43      pyComplex.def_readwrite("real", &Complex::real);
44
45      // 导出它的公有成员 imag,
46      // 该成员在 Complex 类中的位置是 &Complex::imag
47      // 导出到 Python 中之后的名字也是 "imag"
48      pyComplex.def_readwrite("imag", &Complex::imag);
49
50      // 导出它的成员方法 GetArg
51      // 该方法在 Complex 类中的入口是 &Complex::GetArg
52      // 导出到 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
 9
10      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) // 转换到为 float
7  .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)) // 转换到为 float
7  .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() const
100  {
101      return sqrt(real*real + imag*imag);
102  }
103
104  Complex Complex::operator + (const Complex &L) const
105  {
106      return Complex(this->real + L.real, this->imag + L.imag);
107  }
108
109  Complex Complex::operator - (const Complex &L) const
110  {
111      return Complex(this->real - L.real, this->imag - L.imag);
112  }
113
114  Complex Complex::operator + (double L) const
115  {
116      return Complex(this->real + L, this->imag);
117  }
118
119  Complex Complex::operator - (double L) const
120  {
121      return Complex(this->real - L, this->imag);
122  }
123
124  Complex Complex::operator * (const Complex &L) const
125  {
126      return Complex(real*L.real - imag*L.imag, real*L.imag + imag*L.real);
127  }
128
129  Complex Complex::operator * (double L) const
130  {
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 =", b
10  print "c =", c
11  print "d =", d
12  print "a + b =", a + b
13  print "a + 1 =", a + 1
14  print "a * b =", a * b
15  print "a * 2 =", a * 2
16  print "b - a =", b - a
17  print "b - 3 =", b - 3
18  print "|a + b| =", abs(a + b)
19  print "float(a) =", float(a)
20  print "Arg(d) =", d.GetArg()

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



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值