目录
Cython提供了很多方法来搭建C/C++内存和Python对象间的桥梁,但是官方的教程只介绍了一些基础的方法。这篇文章就介绍一下我在各个场合学到和用到的Cython封装(多维)数组的技巧。一般而言这个桥梁会分为两部分,Python与Cython和Cython与C/C++。其中Python中的数组主要形式是list
、array.array
和numpy.ndarray
;Cython中的数组形式有[:,:,:]
(Memoryview/Buffer)和cython.view.array
;C/C++的数组形式有**
(指针)、vector
和Eigen::Vector/Matrix
。
本篇介绍的主要内容也来自于Cython的文档:Typed Memoryviews。
在这里也先介绍一下Cython中的这几个概念:
- Memoryview:这是cython提供的一种语法糖,相当于提供了C中
int[][][]
形式数组的类型。由于Memoryview可以兼容Python的Buffer协议,因此我把他们放在了一起。Memoryview需要指定元素的类型,这个类型必须是内置数值类型或者C结构体。 cython.view.array
:这是Cython提供的一个多维数组类型,与numpy.ndarray
非常相似了。 这两个东西也是可以相互转换的,例如
|
|
Python与Cython数组相互转换
Python与Cython之间的转换基本上都由Cython的Memoryview提供了接口,实际上直接赋值就可以。例如官方给出的这段例子:
|
|
顺带一提,list
对象由于本身不代表一段连续内存,因此需要先转换为array
或ndarray
再赋值给Memoryview。反过来由于Numpy支持Buffer协议,因此Memoryview和Cython的cython.view.array
都可以直接转换为numpy.ndarray
,然后转换为array
和list
:
|
|
以上这些代码中的等式都没有发生内存拷贝。
Cython数组与C/C++数组相互转换
Cython的Memoryview同样承担了大量与C/C++数组进行转换的功能,不过Memoryview只支持一种转换方法,就是与raw指针的相互转换:
|
|
以上代码的等式中也没有发生内存拷贝。
这里需要指出的是,由于指针本身只是一段内存的代表,因此在转换时制定类型和长度(如<double[4]>
),并且需要保证指针指向的数组是C型连续的(多维数组中最后一维的内存是连续的)。如果要将vector
和Eigen::Matrix
转换为Memoryview,那么也同样需要获取其内存指针(vector::data
和Eigen::Matrix::data
)。另外,通过指针转换出来的Memoryview没有引用计数,因此如果你的指针是某个Cython类的成员,那么不要使用指针转换,而使用Buffer协议的方式进行传递。
其他直接转换的方法
除了上面提到的方法之外还有一些直接转换的方法,但是这些方法往往不会做类型和尺寸检查,以及很重要的内存连续性检查(Memoryview会区分C型内存和Fortran型内存),因此使用时需要谨慎。
cdef vector[int] data; cdef list view = data
:Cython提供了list和vector直接转换的接口cdef np.ndarray[double] data; cdef double* view = <double*> data.data
cdef np.ndarray[double, ndim=2] data; cdef double* view = &data[0,0]
cdef array.array data; cdef double* view = data.data.as_doubles[0]
:利用了Cython中的API
非内置类型的转换
在实际应用过程中还会碰到由复杂元素构成的数组(例如PCL里面的PointXYZ、SLAM里会用到的Quaternion),这时就有将复杂类型(通常是自定义struct)在Python和C/C++之间转换的需求。这时可以选择利用Cython提供的MemoryView,也可以利用Python的Buffer协议直接将C++对象传递给Python。
使用Buffer协议的方法请直接参考Cython文档,使用Memoryview的例子如下:
|
|