cython变量的声明
Ctype | Cython cdef statement |
---|---|
Pointers | cdef int *p, void **buf |
Stack-allocted C arays | cdef int arr[10], double points[20][10] |
typedefd aliased types | cdef size_t len |
Compound types(structs and unions) | cdef tm time_struct |
Function Pointers | cdef void (*f)(int, double) |
cython中混合使用静态类型变量和动态类型变量
cython中允许同时使用静态类型的变量和动态类型的变量.例如假设我们有几个静态类型的C变量,想把这些变量放入一个python的元组中,在Cython中,可以进行如下定义:
cdef int a, b, c
tuple_of_ints = (a, b, c)
上面的例子之所以可行是因为C语言的int类型和python语言的int类型有着明显的对应关系,所以python可以自动进行转换。如果a, b, c是C指针的话,那么会行不通. 下表给出了内置的python类型和c/c++类型的对应关系
在python3中,所有的int型对象都是无限的(unlimited precision),当我们将python的int类型转换为C时,cython会检查是否溢出, 如果C类型不能表示python的正向,那么运行的时候会抛出OverFlowError的错误
静态声明python类型的变量
我们知道可以使用cdef来定义c类型的变量,但是也可以用cdef来静态的定义python内置类型的变量,例如list, tuple, dict,或者是扩展类型Numpy arrays。例如
cdef list particles, eggs
cdef dict events
cdef str name
cdef set unique_names
一些除法的模块需要单独提一下。C和python对于计算带符号正式操作的时候有很大的不同。例如-1 % 5在python中等于4 但是在C中等于-1。 当两个整数相除时,Python会检查分母是否为0,为0的话会抛出ZeroDivisionError的错误,而C语言没有这样的机制。Cython使用了Python的语法,即使是C静态类型定义的整数,如果要使用C的语法,可以使用cdivision模块,有集中使用方式,如下:
# cython: cdivision=True
或者是
cimport cython
@cython.cdivision(True)
def divides(int a, int b):
return a / b
或者是
cimport cython
def divides(int a, int b):
with cython.cdivision(True):
return a / b
引用计数和静态字符串变量
python有一个特性是自动内存管理,而Cython通过直接引用计数来实现,Cython有个定期运行的自动垃圾回收机来帮助清理内存(with an automatic garbage collector that runs periodically to clean up unreachable reference cycles). 当同时处理静态类型变量和动态类型变量的时候,Cython的自动内存管理是有意义的,看下面的一个例子.
b1 = b"all men is mortal"
b2 = b"Socrates is a man"
cdef char *buf = b1 + b2
b1 + b2表达式是临时的Python bytes对象,(the assignment attempts to extract the temporary object’s char pointer using Cython’s automatic conversion rules,)因为b1+b2的结果是一个临时对象,所以上述代码运行会报错(Beacuse the temporary result of the addition is deleted immediately after it is created). 正确的方法应该这样
tmp = s1 + s2
cdef char *buf = tmp
或者使用静态类型定义
cdef bytes tmp = s1+ s2
cdef char *buf = tmp
We have to create a temporary bytes object so that Python dose not delete the string data, and we must ensure that the temporary object is maintained as long as the C char* buffer is required.
Cython三种类型的函数
带有def关键字的python函数
Cython支持标准用def定义的python函数,例如下面这个计算阶乘的函数
def py_fact(n):
if n <= 1:
return 1
return n * py_fact(n-1)
这个python函数是有效的cython代码。在Cython中,参数n是动态类型的python变量,当调用py_fact函数的时候,必须传入一个python对象。当然,我们可以告诉Cython将n定义为C的整数类型来进行加速,当然可能会溢出(Python的整型是无限大)
def typed_fact(long n):
if n <= 1:
return 1
return n * typed_fact(n-1)
当我们在cython中定义函数时,可以混合使用Python动态类型的参数和C静态类型的参数。在上述的例子中,静态定义typed_fact函数的参数不会有太多的性能提升。因为typed_fact是python函数,它的返回值是一个python整数类型,不是静态C的long类型, 所以我们要怎样做才能获取性能提升呢?我们可以在Cython中使用cdef关键词来定义C函数
带有cdef关键词的C函数
当我们使用cdef来定义一个函数时,这个函数用的是C的语法。cdef定义的函数的参数类型和返回类型都是静态类型,可以与C指针对象,结构体以及其他不能被强制转换为python类型的C类型一起使用。可以简单的理解为用cdef定义的函数就是使用cython句法定义的c函数。看下面一个例子
cdef long c_fact(long n):
if n <= 1:
return 1
return n*c_fact(n-1)
c_fact()函数和之前定义的typed_fact()函数最大的区别在于,c_fact()函数的参数类型和返回值类型都是声明的静态类型,没有任何的python对象,所以没有python类型到C类型的转换。调用c_fact()和调用纯C定义的阶乘函数是一样有效的。
使用cdef定义的函数可以被同一个源文件下的def函数和cdef函数调用。但是Cython不允许一个cdef定义的函数被外部python代码直接调用。由于这个限制,cdef函数通常用作辅助函数来帮助def函数完成它们的工作。例如,我们想在外部调用c_fast()函数,可以用def来定义一个函数调用c_fact():
def wrap_c_fast(n):
return c_fact(n)
大家可以动手做实验来对比下wrap_c_fact(20)和typed_fact(20),py_fact(20)的时间,可以看出wrap_c_fact(20)运行时间远远短于其他两个函数.
具体的实现如下:
step1: 新建cython文件,test.pyx
cimport cython
@cython.cdivision(True)
def divides(int a, int b):
return a / b
def py_fact(n):
if n < 1:
return 1
return n * py_fact(n-1)
def typed_fact(long n):
if n < 1:
return 1
return n * typed_fact(n-1)
cdef long c_fact(long n):
if n < 1:
return 1
return c_fact(n-1)
def wrap_c_fast(n):
return c_fact(n)
step2: 新建python文件,setup.py
from distutils.core import setup, Extension
from Cython.Build import cythonize
ext=Extension(name='fact', sources=['test1.pyx'])
setup(ext_modules=cythonize(ext))
在terminal中运行 python setup.py -build_ext --inplace
step3: 新建compare.py
import time
from functools import wraps
import fact
def time_wrapper(func):
@wraps(func)
def measure_time(*args, **kwargs):
t1 = time.time()
result = func(*args, **kwargs)
t2 = time.time()
print(f"{func.__name__} took {t2 - t1} seconds")
return result
return measure_time
@time_wrapper
def py_fact(n):
return fact.py_fact(n)
@time_wrapper
def typed_fact(n):
return fact.typed_fact(n)
@time_wrapper
def c_fact(n):
return fact.wrap_c_fast(n)
if __name__ == "__main__":
n = 20
py_fact(n)
typed_fact(n)
c_fact(n)
"""
py_fact took 3.814697265625e-06 seconds
typed_fact took 2.86102294921875e-06 seconds
c_fact took 9.5367431640625e-07 seconds
"""
从结果上可以看出,使用c_fact()运行速度最快
使用cpdef函数将def和cdef进行结合
用cpdef定义的函数是cdef和def的混合体。在Cython中,为了让cdef定义的c_fact()函数可以被python使用,我们用def定义个一个Wrapper function. 然而使用cpdef定义的函数结合了两者的优点,We get a C-Only version of the function and a Python Weapper for it, both with the same name. When we call the function from Cython, we call the C-only version; When we call the function from Python, the wrapper is called. In this Way, cpdef functions combine the accessibility of def functions with the performance of cdef functions.
如下:
cpdef long cp_fact(long n):
if n <= 1:
return 1
return n*cp_fact(n-1)
cpdef函数有一个限制,因为它同时做了python函数和c函数的工作,它的参数类型和返回值类型必须和Python和C兼容。任何python的内置类型都可以用c进行表示,但是C的有些类型,例如void, 指针类型,或者C数组不能用python表示。具体可以看上述的一张表。
函数和异常值处理
在Cython定义的一些函数,例如用cdef和cpdef定义的函数,可能返回的并不是一个python类型的对象,在函数运行报错的时候,可能被抛出一些异常。这个时候我们需要一些机制来处理这些异常情况。看下面一个例子:
cpdef int divide_ints(int i, int j):
return i / j
如果给divide_ints的参数j=0的时候,会有一个ZeroDivisionError的异常。但是对于divide_ints()函数来说,无法抛出这个异常,这个时候需要特殊的机制。
>>> divide_ints(1,1)
1
>>> divide_ints(1,0)
Exception ZeroDivisionError: 'interger division or module by zero' in 'division.divide_ints' ignored
0
运行divide_ints(1,0)的时候警告信息表明这个错误被忽视了,而且给出了错误的结果。但对于Python来说,可以检测到ZeroDivisionError。为了处理这种异常,Cython给出了except关键字来允许cdef或cpdef的函数可以调用python的异常处理模块
cdef int divide_ints(int i, int j) except? -1:
return i / j
except? -1表明如果函数抛出了异常,那么返回值为-1. 这里的-1是任意值,也可以使用其他的数来表示
强制类型转换
python语言和C语言都有强制类型转换的规则. Cython提供了一种非常类似于C语言的类型转换方法,差异在于把括号改成尖括号。例如下面从 void* 转换为 int*的例子
cdef int *ptr_i = <int*> v
相当于C中的
int *ptr = (int*) v
我们也可以用来转换一些python的内置类型或自定义的类型。例如:
def cast_to_list(a):
cdef list cast_list = <list>a
print(type(a))
print(type(cast_list))
cast_list.append(1)
在上面这个例子中,我们将任意一个python类型的对象转换为静态的列表。如果参数a是一个列表或者列表的子类,那么该函数会运行成果,否则会抛出一个异常。当我们不太确定参数的类型的时候,我们可以使用checked casting operator。例如
def cast_to_list(a):
cdef list cast_list = <list?>a
print(type(a))
print(type(cast_list))
cast_list.append(1)
当参数a不是列表或者列表子类型的时候,会抛出一个TypeError的异常。
声明和使用结构体,共用体,枚举类型
Cython同样可以使用C语言中的结构体、共用体及枚举类型。在C语言中,声明一个结构体或共用体:
struct mycpx {
int a;
float b;
};
union uu {
int a;
short b, c
}
在cython中,可以进行如下声明:
cdef struct mycpx:
float real
float imag
cdef union uu:
int a
short a,b,c
cythpn的语法是使用cdef和缩进来声明结构体和共用体。这也是Cython融合python和C的一个地方。
我们可以使用ctypedef关键词来定义一个类型别名,例如:
ctypedef struct mycpx:
float real
float imag
ctypedef union uu:
int a
short a,b,c
这样,定义一个结构体类型的变量可以表示为:
cdef mypyx zz
可以使用三种方式来初始化一个结构体
cdef mycpx a = mycpx(3.14, -1.0)
cdef mypyx b = mycpx(real=2.718, imag=1.618)
或者
cdf mypyx zz
zz.real = 3.14
zz.imag = -1.0
或者使用一个python的字典进行初始化。cython会对每一个赋值进行自动转换。
cdef mypyx zz = {'real':3.14, 'imag':-1.0}
对于枚举类型,可以用多行代码定义成员变量或者用一行代码。枚举类型可以用cdef或者ctypedef来定义。就像之前定义结构体一样。
cdef enum PRIMARIES:
RED = 1
YELLOW = 3
BLUE = 5
cdef enum SECONDARIES:
ORANGE, GREEN, PURPLE
用ctypedef定义类型别名
cython可以用ctypedef关键词来支持C中类型别名。和C语言中的使用方式很相似。下面是一个简单的例子
ctypedef double real
ctypedef long integral
def dislayment(real d0, real d1, real a, real t):
cdef real d = d0 + (v0 * t) + (0.5 * a * t**2)
return d
ctypedef功能对于C++来说非常有用,尤其是可以使用typedef来简化很长的模板类型的时候。注意,ctypedef不能再函数内部使用,而且必须在文件中出现才能使用。
Cython预处理器
Cython用DEF关键词来定义宏,运行的时候当成一个常量,类似于c预处理器#define. 对于一些很长的数字,有些时候非常有用,例如
DEF E = 2.71828182845904
DEF PI = 3.141592653589793
def test():
return E * PI + 1.0