用cython包装C++库
假设有一个简单的生成随机数的类, 提供了一下接口
namespace mtrandom {
const static unsigned int N = 624;
class MT_RNG {
public:
MT_RNG();
MT_RNG(unsigned long s);
MT_RNG(unsigned long init_key[], int ket_length);
void init_genrand(unsigned long s);
unsigned long genrand_int32();
double genrand_real1();
private:
unsigned long mt[N];
int mti;
};
}
Cython只能包装public方法和成员,不能访问任何privated或者protected方法和成员。
为了在Cython中使用这个类接口,可以使用extern关键词,有三个要求:
- Declaring the c++ namespace with cython namespace clause
- Using the cppclass keyword to declare a C++ class interface block
- Declaring the class’s interface in this block
因为MT_RNG是在mtrandom命名空间中声明的,在cython中使用cdef extern必须说明命名空间,
,在extern代码快内部,声明了常数N, 以及用cppclass关键词声明MT_RNG c++类, 并且在该类内部,将所有想在cython中使用的的Public结构体,函数,类成员都放进去,这样在Cython代码中就可以访问。
cdef extern from "mt19937.h" namespace "mtrandom":
unsigned int N
cdef cppclass MT_RNG:
MT_RNG(unsigned long s)
MT_RNG(unsigned long init_key[], int key_length)
void init_genrand_int32()
unsigned long genrand_int32()
double genrand_real1()
在cython中一般使用扩展类型包装c++类。定义一个扩展类型RNG, 并且该扩展类型有一个指针指向被包装的c++类, 用__cinit__方式进行初始化, 用__dealloc__进行内存回收。__dealloc__只在程序结束的时候使用,并且只调用一次。现在可以通过cpdef定义一些类成员函数来生成随机数(通过_thisptr指针),下面是一个包装c++类的范例。
cdef class RNG:
cdef MT_RNG *_thisptr
def __cinit__(self, unsigned long s):
self._thisptr = new MT_RNG(S)
def __dealloc__(self):
if self._thisptr != NULL:
del self._thisptr
cpdef unsigned long randint(self):
return self._thisptr.genrand_int32()
cpdef double rand(self):
return self._thisptr.genrand_real1()
编译
当我们编译一个c++项目的时候,我们需要说明使用c++语言,并且包含c++的源文件。例如,要编译RNG.pyx文件,需要新建一个setup.py,包含的代码如下:
from distutils.core import setup, Extension
from Cython.Build import cythonize
ext = Extension("RNG", source=['RNG.pyx', 'mt19937.cpp'], language='c++')
setup(name="RNG", ext_modules=cythonize(ext))
重载函数和方法
对于MT_RNG类来说,不同的参数对应不同的构造函数,那么在python中,该如何调用呢?因为python不支持函数的重载,所以只能在__cinit__中检查参数的类型,使用对应的c++构造函数. 为此,需要python标准库中的内置array(要求内置的元素类型都一样),cython知道如何在python和c++中处理array类型的变量。
from cython.array import array
cdef class RNG:
cdef MT_RNG *_thisptr
def __cinit__(self, seed_or_state):
if isinstance(seed_or_state, int):
self._thisptr = new MT_RNG(seed_or_state)
else:
state_arr = array("L", seed_or_state)
self._thisptr = new MT_RNG(state_arr.data.as_ulongs, len(state_Arr))
在cython代码中直接使用C++类
我们已经知道怎么用扩展类型来包装c++类,这也是在cython使用c++类最常见的做法。当然,也可以直接在Cython代码里面使用C++类,可以使用stack-allocate一个MT_RNG实例,并且在程序结束的时候清理这个实例。为了使用c++对象,同样需要在cef cppclass代码块中进行声明:
cdef extern from "mt19937.h" namespace "mtrandom":
cdef cppclass MT_RNG:
MT_RNG()
void init_genrand(unsigned long s)
现在可以在函数中对MT_RNG对象进行一些操作
def make_random_list(unsigned long seed, unsigned int len):
cdef:
#...
MT_RNG *rng
rng = new MT_RNG(seed)
try:
#...
finally:
del rng