在 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: 内置函数及其调用的特殊方法 | |||||||||||||||||||||||||||||||||||||||||||||||||
|
例如,我可以给 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 总结
包装一个类,我们写的胶水代码中主要用到了如下几个类和类模板:
boost::python::class_<>和 boost::python::init<>
class_<>用来包装类;
class_<>::def() 方法用来包装类的方法;
class_<>::def_readwrite() 方法用来包装类的成员;
class_<>::def_readonly() 方法用来包装类的成员,并使它在 Python 中只读;
init<X, Y, Z> 表示类的一个构造方法接受的三个参数,分别是 X,Y,Z 类型;
boost::python::self 和 boost::python::other<>
self 表示重载的运算符中,操作数之一是对象本身
other<T> 表示重载的运算符中,操作数之一是 T 类型的对象
boost::python::base<>, 举例:class_<X, base<Y> > 表示我们要包装的类 X 继承了 类Y。
本文介绍了将 C++ 类导出到 Python 的方法。包括包装简单类,导出构造方法、成员方法和公有成员变量;特殊方法和运算符重载,注意 Python 中特殊方法的含义;处理类的继承,使用类模板确保 Python 能识别子类关系。还总结了包装类用到的类和类模板。
2638

被折叠的 条评论
为什么被折叠?



