最近的的科研工作涉及到Cython的应用。以前没有接触过Cython,对这个基本不了解。如果不涉及到代码接口的改变,那么我可以学习前辈们的代码,照葫芦画瓢来改代码。可是那样的代码不够简介美观,Pull Request的Reviewer指出代码接口需要调整简化。
没办法,只能硬着头皮改。然而前辈们的代码中没有找到合适的参照,自己假象着改几下结果错误越來越多。不能像无头苍蝇一样改了,必须得学会Cython的基础使用方法。
我需要实现的功能是:有一个C++的程序,写一个Cython的接口,在python中调用C++中的函数。参考相关的官方文档,这是一个简单的几何方面的库。按照官方文档走,结果还是走了不少弯路,有写东西官方文档上也没有写的很详细。
下面就将我学习这个例子的过程及需要注意的细节总结一下。
C++代码部分
h文件
1 2 3 4 5 6 7 8 9 10 11 12
| namespace shapes { class Rectangle { public: int x0, y0, x1, y1; Rectangle(); Rectangle(int x0, int y0, int x1, int y1); ~Rectangle(); int getArea(); void getSize(int* width, int* height); void move(int dx, int dy); }; }
|
cpp文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include "Rectangle.h"
namespace shapes {
Rectangle::Rectangle() { }
Rectangle::Rectangle(int X0, int Y0, int X1, int Y1) { x0 = X0; y0 = Y0; x1 = X1; y1 = Y1; }
Rectangle::~Rectangle() { }
int Rectangle::getArea() { return (x1 - x0) * (y1 - y0); }
void Rectangle::getSize(int *width, int *height) { (*width) = x1 - x0; (*height) = y1 - y0; }
void Rectangle::move(int dx, int dy) { x0 += dx; y0 += dy; x1 += dx; y1 += dy; }
}
|
pyx文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| cdef extern from "Rectangle.h" namespace "shapes": cdef cppclass Rectangle: Rectangle() except + Rectangle(int, int, int, int) except + int x0, y0, x1, y1 int getArea() void getSize(int* width, int* height) void move(int, int)
cdef class PyRectangle: cdef Rectangle c_rect def __cinit__(self, int x0, int y0, int x1, int y1): self.c_rect = Rectangle(x0, y0, x1, y1) def get_area(self): return self.c_rect.getArea() def get_size(self): cdef int width, height self.c_rect.getSize(&width, &height) return width, height def move(self, dx, dy): self.c_rect.move(dx, dy)
|
setup.py文件
1 2 3 4 5 6 7 8
| from distutils.core import setup from Cython.Build import cythonize
setup(ext_modules = cythonize( "rect.pyx", sources=["Rectangle.cpp"], language="c++", ))
|
编译
前面的文件内容在官方文档上写的还算清楚,但编译和使用部分,官方文档写的就很不清楚了。尤其是官方文档这里没有介绍编译cpp文件,只说不链接这个东西会出现undefine symbol的错误。这部分内容参考了这里。
编译C++文件
编译C++文件生成.o文件,C++ Objects在这个文件里面。
运行setup
1
| $ python setup.py build_ext --inplace
|
这一步会生成rect.so,但是这个文件暂时还不能使用,在python中import rect的话会出现ImportError: ./rect.so: undefined symbol错误。
链接rect.so与Rectangle.o
1
| $ gcc -shared -fPIC -I/usr/include/python2.7 rect.cpp Rectangle.o -lstdc++ -o rect.so
|
测试运行
下一个脚本run.py,放入如下内容:
1 2
| import rect print(rect.PyRectangle(0, 0, 1, 2).get_area())
|
运行该脚本,如果流程正确,应该会得到输出:
总结反思
通过这个例子,不难看出:
- Cython连接C++的函数是是通过Python类的函数->cppclass->C++函数实现的。
- Python类的函数名称不一定要和C++函数名称一直,只要接口定义好到底调用哪个函数就行。
- Python类的函数数目也不一定要和C++函数一致,只需要保证Python想调用的C++函数已经定义且能够找到就行。