Organizing Cython code
Cython提供了三种类型的文件来组织Cython代码,最常用的是.pyx文件,也被叫做执行文件。如果Cython项目很小而且不需要额外的代码访问C-level的类或者结构体,那么一个.pyx文件就够了。但是如果需要C-level的类,那么可以将c-level的类定义在另外一个文件中。
假设现在有一个执行文件simulatior.pyx,包含一下内容(省略的函数体的定义)
- A ctypedef
- A cdef class named State to hold the simulation state
- Two def functions, setup and output
- Two cpdef functions
文件的大致结构如下
ctypedef double real_t
cdef class State:
cdef:
unsigned int n_particles
real_t *x
real_t *vx
def __cinit__(...):
#...
def __dellloc__(...):
#...
cpdef real_t momentum(self):
# ...
def setup(input_name):
pass
cpdef run(State st):
pass
cpdef int step(State st, real_t timestep):
pass
def output(State st):
pass
State扩展类型有__cinit__和__dealloc__方法用于分配和回收,还有一个cpdef定义的方法。因为所有的代码都在一个文件中,所有的函数都可以使用C-level的State类。如果该文件代码过长,为了方便阅读,可以将C-level的类的声明或者函数写在另一个**.pxd文件**中. 首先,新建一个文件simulator.pxd,在这个文件中声明C-level的类和函数。
ctypedef double real_t
cdef class State:
cdef:
unsigned int n_particles
real_t *x
real_t *vx
cpdef real_t momentum(self):
cpdef run(State st)
cpdef int step(State st, real_t timestep)
注意,因为.pxd文件是定义文件(definition file), 只在编译的时候访问,所以我们只将C-level的声明写在.pxd文件中。Python-only的声明,例如用def定义的函数,是不能写入.pxd中的,否则会出现编译错误。我们的执行文件simulator.pyx同样需要改变,因为simulator.pxd和simulator.pyx有同样的名字,Cython认为它们有同样的命名空间,我们不能在simulator.pyx中进行重复声明,这样做会出现编译错误。现在我们的执行文件如下(simulator.pyx)
cdef class State:
def __cinit__(...):
#...
def __dellloc__(...):
#...
cpdef real_t momentum(self):
# ...
def setup(input_name):
pass
cpdef run(State st):
pass
cpdef int step(State st, real_t timestep):
pass
def output(State st):
pass
可以看出ctypedef和State的属性已经被移到了定义文件(simulator.pxd)中, 函数体的定义到保留在执行文件中。当编译simulator.pyx的时候,Cython编译器会自动检测simulator.pxd,使用它的声明,定义文件中可以包含的内容如下:
定义文件中不能包含:
cimport语句
假设我们现在有另外一个版本的improved_simulator.pyx, 这个版本的simulator想使用我们之前定义的pyx中的setup和step函数,而且improved_simulator中会有一个子类继承simulator.pyx中的State类。improved_simulator.pyx文件如下:
from simulator cimport State, step, real_t
from simulator import setup as sim_setup
cdef class NewState(State):
cdef:
#...extra attribute
def __cinit__(self, ...):
pass
def __dealloc__(self):
pass
def setup(fname):
pass
cpdef run(State st):
pass
在improved_simulator.pyx的第一行,使用cimport语句来访问State扩展类,cpdef定义的step,以及ctypedef定义的real_t. 这个访问是C-level而且发生在编译的时候。cimport语句寻找simulator.pxd定义文件,而且只有声明之后才可以导入。import语句用来导入simulator扩展类型中def定义的函数,在程序运行的时候导入。
cimport语句和import语句有相同的语法。可以用cimport导入.pxd文件,像模块与样使用, 例如:
cimport simulator
# ...
cdef simulator.State st = simulator.State(params)
cdef simulator.real_t dt = 0.01
simulator.step(st, dt)
也可以提供模块的别名
cimport simulator as sim
# ...
cdef sim.State st = sim.State(params)
cdef sim.real_t dt = 0.01
sim.step(st, dt)
注意,当使用cimport导入python-level对象的时候会出现编译错误。相反,当使用import导入C-Only声明的时候也会出现编译错误。对于cpdef定义的函数或者扩展类型,可以使用import或者cimport(recommened)。
定义文件中可以包含extern blocks. 例如可以在twister.pxd中声明一些函数
cdef extern from "mt19937.h":
void init_genrand(unsigned long s)
unsigned long genrand_int32()
...
在.pyx执行文件中,可以用cimport导入需要用到的函数
from twister cimport init_genrand
Predifined Definition Files
Cython有很多预先定义好的定义文件(.pxd), 这些i那个一文件都被打包好放在Cython源文件夹下的includes目录中。包含c语言的标准库libc(有stdlib, stddio, math, string, stdint等.pyx文件),C++的标准哭libcpp(stringm vector, list, map, set, pair)。基本用法如下:
# using cimport with a module in a package
from libc cimport math
math.sin(3.14)
# using cimport with an object from a dotted module name
from libc.math cimport sin
sin(3.14)
# multiple named cimports
from libc.stdlib cimport rand, srand, qsort, malloc ,free
cdef int *a = <int*>malloc(10 * sizeof(int))
# using cimport with an alias
from libc.string cimport memcpy as c_memcpy
# using cimport with c++ stl template class
from libcpp.vector cimport vector
cdef vector[int] *vi = new vector[int](10)
如果使用cimport和import导入不同的函数但是有相同的名字,Cython会出现编译错误. 可以用别名进行区分
# compile time error
from libc.math cimport sin
from math import sin
定义文件(.pxd)和c/c++的头文件很相似:
- They both declare C-level constructs for use by external code
- They both allow us to break up what would be one large file into several components
- They both declare the public C-level interface for implementation