Cython的一些小Trick

在使用 Python 中,可能会有调用 C++程序的需求,Cython 是一个不错的选择(也没好到哪去)。

Cython 的环境

可以直接使用conda安装 Cython。通常不会遇到编译器的问题。

和 setup.py 工具联合使用

setup.py中进行下列配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Cython.Build import cythonize
from setuptools import Extension
...

extensions = [ # find_pyx
Extension("**.**.**", # module name
["src/**/**/**.pyx"], # absolute path of pyx file
include_dirs=[numpy.get_include(), "e3rdpackage/boost"],
# some 3rd package include dirs, such as numpy, boost.
# same to `include_directories()` in `CMakeLists.txt`
language="c++",
# if using CPP.
)
]
setup(...
ext_modules=cythonize(extensions, language_level=3),
)

则使用setup.py 安装时会对pyx文件进行编译。

pyx文件的trick

比较通用的调用方式

如果目标的hpp文件还引用了大量的模板,则建议在hpp中封装一个仅使用原生类型的类和成员函数。
然后在pyx中引用该类的定义,并封装python的接口。

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
33
34
35
36
37
38
39
40
41
42
import numpy as np
cimport numpy as np
from libcpp.vector cimport vector # 用于CPP的原生vector类型
...

cdef extern from "****.hpp" namespace "****":
cdef cppclass <class_name>:
int ****
...
<class_name>(int ****, int* ****) except + # 初始化函数和传入引用参数,except用于说明捕获异常
...

void func(vector[int]* <vector_para_to_change_in_cpp>) # 希望在CPP程序中更改的一个vector数组

cdef class py_<class_name>:
cdef <class_name> *<c_instance_name>
def __cinit__(self, int ****, int[:,:] ****):
self.<c_instance_name> = new <class_name>(****,&****[0,0])
# 使用Meomeryview`[:]`传入numpy等内存缓冲区参数,使用&**[0,0]等同于传入数组指针

...

# 通过new定义的实例需要使用__dealloc__处理回收
def __dealloc__(self):
del <c_instance_name>


def func(self):
cdef vector[int]* <vector_name> = new vector[int](<vector_length>)
# 声明特定大小的vector
self.c_finder.<some_cpp_func_except_a_vector_argument>(&<vector_name>[0])
# 将指针&<name>[0]传入CPP函数
<numpy_array_name>=np.asarray(<vector[int]&> <vector_name>[0])
# 使用<vector[int]&>对vector指针进行转换,并通过np.asarray()返回numpy类型供python使用。
del <vector_name>
# 记得回收声明的vector指针,防止内存泄漏,这个指针在转换出numpy类型后就可以回收了
return <numpy_array_name>

@property
def <some_attribution>(self):
return self.<c_instance_name>.****()
# 返回一些变量属性

则可以在python脚本中调用相应的CPP函数:

1
2
3
4
5
6

from **.** import <module name>

<instance_name>=<module name>.<class>(**,**)
<numpy_array_name>=<instance_name>.func()
# numpy_array_name会被赋值为返回的数组