我们前篇谈到了Cython的访问控制,并且谈论了cdef class关键字的底层操作,顺带也谈论了Python类为什么会比Cython类慢的原因。本篇我们将介绍Cython扩展类的初始化
Cython扩展类实例实例化,C的运行时系统在内存中都为其实例持有一个C结构体的内存区域,对于对象的创建和初始化,当Python调用__ init __时,self参数必须是该扩展类型的有效实例,当调用 __ init __时,通常使用来自参数来初始化类实例属性,但在C底层,在调用 __ init __之前,必须为其扩展类型的实例分配内存,并且结构体的字段都必须处于有效状态。
__ cinit __和__dealloc __方法
__ cinit__是负责执行C级别的类实例属性(指针类型)的内存分配和初始化,并且在外部代码的执行结束前,Cython编译器会默认隐含调用扩展类实例的 __ dealloc __方法,这些行为和C++编译器是一致。
- __ cinit__等同C++类中默认构造函数
- __ dealloc__ 等同于C++类的析构函数
使用__ cinit__的注意事项
- __ cinit__可能会带来额外的开销
- __ cinit__的参数声明和__ init__必须一致,因为它们会被同时调用,因此__ cinit__的参数会留下kargs,*kwargs
- __ cinit__中涉及到C指针类型的类属性的内存分配,必须在类定中显式定义__ dealloc__,因为需要通过__ dealloc __对C指针类型的实例属性进行内存释放
- __ cinit__和__ init __只能使用def关键字申明
我们通过一些例子来列举使用 __ cinit 的情况,下面的是一个Cython扩展类Fruit,在C级别的扩展类,若定义了C指针类型的类属性,在 cinit__方法内完成C类型的类实例属性的初始化和内存分配,是其主要的用途
from libc.stdlib cimport malloc,free
cdef class Fruit(object):
'''Fruit Type'''
cdef readonly str name
cdef public double qty
cdef readonly double price
cdef double *weight
def __cinit__(self,nm,qt,pc):
print("__cinit__ method executed")
self.name=nm
self.qty=qt
self.price=pc
self.weight=<double*>malloc(sizeof(double))
self.weight[0]=23.33
print("weight: ",self.weight[0])
def __init__(self,nm,qt,pc):
print("__init__ method executed")
def amount(self):
return self.qty*self.price
def __dealloc__(self):
print("__dealloc__method executed")
if self.weight!=NULL:
free(self.weight)
调用代码
#!/usr/bin/python3
import pyximport
pyximport.install()
import cy_fruit
if __name__=='__main__':
b=cy_fruit.Fruit("banana",23.0,33.0)
b.amount()
Cython扩展类在构造时会保证__ cinit__只能被调用一次,并且在__ init__,__ new__或其他Python级别的构造函数(例如,类方法构造函数)之前被调用, Cython将所有初始化参数传递给 __ cinit __,并在./app.py的Python外部代码在结束之前,隐式调用Fruit类实例b. __ dealloc __
Python类和Cython扩展类的实例化
我们先来比较Cython扩展类和Python类他们的初始化方式,如果你对__ new __ 和 __ init __ 存疑可以看我这篇随笔《第6篇:Python类的__ new __和 __ init __执行原理》,再进行下面的话题
对于Cython扩展类来说,我们使用显式调用Fruit. __ new __会比常规Fruit(...)语句执行实例化会少一个步骤,因为显式调用Fruit. __ new __会默认只执行Fruit. __ cinit __构造函数
对于Python类来说,显式调用Fruit. __ new __会比常规Fruit(...)语句执行实例化会少一个步骤,因为显式调用Fruit. __ new __,会默认只执行Fruit. __ new __方法
Ok,从上面的Cython类和Python类的实例化路径的比较,可以有一些有趣的总结
我们说对象的实例化,必须首先生成对象,然后才能构造对象,我们将Python类的 __ new __方法和Cython类的 __ cinit __方法称为对象生成函数,并且Cython和Python允许我们将对象构造(对象的属性初始化和对象方法的执行)的步骤,浓缩到对象生成函数中,当我们使用类似的语句“CLASS. __ new __(CLASS,....)”能快速生成并构造对象,我们将“CLASS. __ new __(CLASS,....)”这样的语句调用称为对象快速实例化,通过大量代码测试,对象快速实例化和常规的对象实例化方法的性性能差距一般情况下是10-30us的差距,在Cython类中,对象快速实例化甚至会比常规的对象实例化快100us的情况都存在,如下图
对于需要在内存中快速创建成千上万个类对象的应用场景,以最低的时间开销完成这些操作必须优先考虑,那么上面的对象快速实例化
Cython扩展类的快速实例化
Cython提供了两种方法来加速扩展类型的实例化。 对于Cython扩展类型快速的实例化方案就是显式调用__ new__方法,我们从下面的示例代码,我们没必要在类实现中定义__ init __方法,另外需要注意的是Cython的类定义语义中不存在 __ new __方法的定义,因为Cython类语义 __ cinit __做的事情就相当与Python类的 __ new __,像下面的代码Fruit. __ new __(Fruit,52,32)的语句调用,实际上是调用Fruit类内部的 __ cinit __方法
#cython:language_level=3
cdef class Fruit(object):
'''Fruit Type'''
cdef readonly str name
cdef public double qty
cdef readonly double price
def __cinit__(self,str nm,double qt,double pc):
self.name=nm
self.qty=qt
self.price=pc
def amount(self):
return self.qty*self.price
def __repr__(self):
return "name:{},qty:{},price:{}".format(
self.name,self.qty,self.price)
#end-class
常归对象实例法 vs 对象快速实例化 - 性能比较
下面我们看看分别使用对象快速实例法和普通的对象实例化法去创建10000个Fruit对象的性能对比,首先我们都有数据源,下面是一个非常高效的read_excel函数,从excel工作簿数据加载到一个Cython类型的list当中,该函数的原本定义,请参考我之前的随笔《Python的openpyxl性能加速》
%%cython
from openpyxl import load_workbook
cpdef list read_excel(str filename):
wb=load_workbook(filename,read_only=True)
ws=wb['sheet1']
cdef list data=list(),neRow
cdef int rdx,cdx
for rdx,row in enumerate(ws.rows):
neRow=[]
for cdx,cell in enumerate(row):
neRow.append(cell.value)
if len(neRow):
data.append(neRow)
return data
快速实例化的代码主体
from cy_fruit import Fruit
cdef list data=read_excel("./Vegetable_Fruits.xlsx")
cdef list item,fruitList
for item in data:
fruit.append(Fruit.__new__(Fruit,item[1],item[2],item[3])
del item
第二个性能改进适用于经常连续创建和删除的类型,以便它们可以从空闲列表中受益。 Cython为此提供了装饰器@ cython.freelist(N),它为给定类型创建了N个实例的静态大小的空闲列表。 例:
cimport cython
@cython.freelist(8)
cdef class Penguin:
cdef object food
def __cinit__(self, food):
self.food = food
penguin = Penguin('fish 1')
penguin = None
penguin = Penguin('fish 2') # does not need to allocate memory!
更新中....