0%

Cython中的特殊函数

这次来介绍一下Cython中的特殊函数定义,Cython相比Python本身的特殊函数之外还增加了一些新的函数,用来满足对C特性的支持,其中有些内容还经常令人混淆。关于Python中特殊变量和特殊函数名的内容,请参考Python官方文档

def, cdefcpdef

首先最开始需要分清的便是Cython中的三种函数类型。def定义的对象(包括变量、函数、类型)都是普通的Python对象,是Python可以直接调用的,因此其参数都只能是Python类型或对象;cdef定义的对象则是C/C++层面的,可以直接用C/C++对象作为参数,因此不能被普通Python代码调用,这样减少了很多overhead因此可以提高运行效率。另外尽管cdef的函数不是Python对象,无法当作变量使用,但还是可以获取函数指针的。而cpdef则是同时兼具两方面特性,其本质是用cdef定义函数后再用def定义一个函数封装,使得在Cython中调用时可以调用高效的cdef版本,而在Python中调用的是与Python兼容的def版本。

__init____cinit__

在理清了上面几个关键字后另一个经常令人疑惑的点便是__init____cinit__的区别。__cinit____dealloc__都是Cython特有的特殊函数。官方文档在其用法上解释的并不清楚,只是说__cinit__可以用来进行C/C++级别的初始化。实际上,使用__cinit__的重要原因是源于其特性:__cinit__会像C++一样自动执行基类的__cinit__,因此它保证会在构造时被执行一次(且只被执行一次)。由于Python中的__init__函数默认不会调用基类的__init__,因此如果想保证类型中的cdef成员被初始化,避免可能的堆栈问题(如指针没有初始化),那么就可以选择使用__cinit__。如果理解了这一点就可以知道,什么时候需要使用__cinit__了。

但是使用__cinit__的时候有很多限制需要了解: 1. __cinit__有时会带来额外的开销,这篇博客中有一些分析。 2. __cinit__的参数声明和__init__必须一致,因为会同时被调用。因此通常__cinit__的参数中会留下*kargs**kvargsStackoverflow上也有人问过这个情况。 3. __cinit__中如果要用malloc分配内存,记得在__dealloc__中销毁。__dealloc__相当于C++版本的__del__ 4. __cinit____init__一样也只能使用def声明,不能用__cdef____cpdef__。具体原因我并不清楚。

运算符重载

其他大多数的特殊函数定义和用法几乎和Python相同,但是需要特别指出的是运算符重载的部分。以加法为例,在Python中加法a + b的实现方式是: 1. 如果a中定义了__add__,那么调用a.__add__(b) 2. 如果a中没有定义,而b中定义了__radd__,那么调用b.__radd(a)

而在Python的C扩展类里(包含Cython和pybind11的实现),其实现方式是寻找接受ab类型的__add__重载,也就是说本质上在C扩展类中定义的__add__都是__add__的重载,这也是与C++的operator重载理念一致,只不过这个__add__仍然需要定义在类里。在Cython文档中给出的运算符列表里,参数里带self的函数都是按照Python中的方法实现的,self不能指定类型;而以x, y这种形式为参数的则是按照C扩展类执行方式的函数,xy都可以指定类型。

另外Cython还定义了一个特殊的运算符函数__richcmp__,这个是Python中没有的,不过其功能只是把比较符号(>,<,=)的实现合并了,与Python的__eq____lt__等函数没有本质区别。这在官方文档中也有说明

__getbuffer__

Cython中有两个版本的Buffer协议,一种是提案PEP-3118定义的,另一种是Python官方定义之前Cython自己的定义方式。其中前者在之前介绍Cython封装的文章中已有介绍,就不多赘述。其相关的特殊函数是__getbuffer____releasebuffer__,这两个函数也都是Cython特有的。而后者比较难用,已经被标记为depricated废弃了,也不介绍了。

属性(property)

Cython中还提供了一套非常方便的属性定义方法。原本在Python中定义属性非常但疼,例如下面的代码定义了名为length的属性,使得你可以通过square.length的方法访问它

1
2
3
4
5
6
7
8
9
10
class Square:
@property
def length(self):
return self._length
@length.setter
def length(self, value):
self._length = value
@length.deleter
def length(self):
self._length = 0

而在Cython中定义属性就更简单了,它除了支持上面的方法外还有另一种更加直观的定义方式(虽然这个方式也已经被标记为depricated了):

1
2
3
4
5
6
7
8
cdef class Square:
property length:
def __get__(self):
return self._length
def __set__(self, value):
self._length = value
def __del__(self):
self._length = 0


Cython的类型还有各种其他的奇奇怪怪的小特性,在Cython的这两篇文档里有详细介绍:Extension Types, Special Methods of Extension Types,仅供参考~

Treat me some coffee XD