github地址

使用Cython导入库的话,需要一下几个文件:

.c:C函数源码

.h:C函数头

.pxd:Cython函数头

.pyx:包装函数

setup.py:python

本节示例.c和.h文件同『Python CoolBook』使用ctypes访问C代码_下_demo进阶即存在sample.c和sample.h两个源文件。

cdef:Cython函数,只能在Cython中调用,python识别不了这个定义后面的主体,而且它后面也不仅仅接函数,class等均可,def定义的函数可以被Python识别,在pxd和pyx中均可使用。

csample.pxd:Cython头文件

.pxd文件仅仅只包含C定义(类似.h文件),即相当于将.h文件包装为Cython的头。注意,.pxd仅仅是声明定义,我们此时并未对函数做包装,这个工作在.pyx中完成。

# csample.pxd
#
# Declarations of "external" C functions and structures cdef extern from "sample.h":
int gcd(int, int)
bint in_mandel(double, double, int)
int divide(int, int, int *)
double avg(double *, int) nogil ctypedef struct Point:
double x
double y double distance(Point *, Point *)

为例对比,我们给出sample.h文件如下:

#ifndef __SAMPLE_H__
#define __SAMPLE_H__
#include <math.h> #ifdef __cplusplus
extern "C" {
#endif int gcd(int x, int y);
int in_mandel(double x0, double y0, int n);
int divide(int a, int b, int *remainder);
double avg(double *a, int n); /* A C data structure */
typedef struct Point {
double x,y;
} Point; double distance(Point *p1, Point *p2); #ifdef __cplusplus
}
#endif
#endif

sample.pyx:Cython封装主体

程序如下,pyx文本中语法和python一致,但是却可以像C中一样指定形参类型(也可以不指定),实际上这里是最基础的包装,可以看到就是调用了csample包中的函数,我们在后文中可以看到对基本包装的加强。

# sample.pyx

# Import the low-level C declarations
cimport csample # Import some functionality from Python and the C stdlib
from cpython.pycapsule cimport * from libc.stdlib cimport malloc, free # Wrappers
def gcd(unsigned int x, unsigned int y):
return csample.gcd(x, y) def in_mandel(x, y, unsigned int n):
return csample.in_mandel(x, y, n) def divide(x, y):
cdef int rem
quot = csample.divide(x, y, &rem)
return quot, rem def avg(double[:] a):
cdef:
int sz
double result sz = a.size
with nogil:
result = csample.avg(<double *> &a[0], sz)
return result # Destructor for cleaning up Point objects
cdef del_Point(object obj):
pt = <csample.Point *> PyCapsule_GetPointer(obj,"Point")
free(<void *> pt) # Create a Point object and return as a capsule
def Point(double x,double y):
cdef csample.Point *p
p = <csample.Point *> malloc(sizeof(csample.Point))
if p == NULL:
raise MemoryError("No memory to make a Point")
p.x = x
p.y = y
return PyCapsule_New(<void *>p,"Point",<PyCapsule_Destructor>del_Point) def distance(p1, p2):
pt1 = <csample.Point *> PyCapsule_GetPointer(p1,"Point")
pt2 = <csample.Point *> PyCapsule_GetPointer(p2,"Point")
return csample.distance(pt1,pt2)

由于很多细节都蕴含在上面代码中,也涉及很多前面介绍过的高级特性,包括数组操作、包装隐形指针和释放GIL,所以下面逐个分析各个函数。

各种情况函数分析

gcd:简单的数字参数函数

csample.pxd 文件声明了 int gcd(int, int) 函数, sample.pyx 中的包装函数如下:

cimport csample

def gcd(unsigned int x, unsigned int y):  # <--- 无符号整形
return csample.gcd(x,y)

无符号整型使得在运行中接收到负数会报这一行的错误,我们可以修改如下,

# def gcd(unsigned int x, unsigned int y):
# return csample.gcd(x, y)
def gcd(int x, int y):
if x <= 0:
raise ValueError("x must be > 0")
if y <= 0:
raise ValueError("y must be > 0")
return csample.gcd(x,y)

可以看到,这里对Python语句支持的很好。

in_mandel:返回值为0或1(布尔整形)

/* Test if (x0,y0) is in the Mandelbrot set or not */
int in_mandel(double x0, double y0, int n) {
double x=0,y=0,xtemp;
while (n > 0) {
xtemp = x*x - y*y + x0;
y = 2*x*y + y0;
x = xtemp;
n -= 1;
if (x*x + y*y > 4) return 0;
}
return 1;
}

pxd声明可以指定函数返回类型bint:

bint in_mandel(double, double, int)

divide:形参指针对象

int divide(int a, int b, int *remainder) {
int quot = a / b;
*remainder = a % b;
return quot;
}

python没法传递一个地址,但pyx可以

def divide(x, y):
cdef int rem
quot = csample.divide(x, y, &rem)
return quot, rem

在这里,rem 变量被显示的声明为一个C整型变量。 当它被传入 divide() 函数的时候,&rem 创建一个跟C一样的指向它的指针。

avg:形参数组&GIL释放

/* Average values in an array */
double avg(double *a, int n) {
int i;
double total = 0.0;
for (i = 0; i < n; i++) {
total += a[i];
}
return total / n;
}

avg() 函数的代码演示了Cython更高级的特性:

def avg(double[:] a):
cdef:
int sz
double result sz = a.size
with nogil:
result = csample.avg(<double *> &a[0], sz)
return result

首先 def avg(double[:] a) 声明了 avg() 接受一个一维的双精度内存视图。 最惊奇的部分是返回的结果函数可以接受任何兼容的数组对象,包括被numpy创建的。例如:

>>> import array
>>> a = array.array('d',[1,2,3])
>>> import numpy
>>> b = numpy.array([1., 2., 3.])
>>> import sample
>>> sample.avg(a)
2.0
>>> sample.avg(b)
2.0
>>>

在此包装器中,a.size&a[0] 分别引用数组元素个数和底层指针。 语法 <double *> &a[0] 教你怎样将指针转换为不同的类型。 前提是C中的 avg() 接受一个正确类型的指针。 参考下一节关于Cython内存视图的更高级讲述。

除了处理通常的数组外,avg() 的这个例子还展示了如何处理全局解释器锁。

  1. 语句 with nogil: 声明了一个不需要GIL就能执行的代码块。 在这个块中,不能有任何的普通Python对象——只能使用被声明为 cdef 的对象和函数(pxd中的)。
  2. 另外,外部函数必须现实的声明它们能不依赖GIL就能执行。 因此,在csample.pxd文件中,avg() 被声明为 double avg(double *, int) nogil .

distance、Point:结构体处理

本节使用胶囊对象将Point对象当做隐形指针来处理,pxd中声明如下,

ctypedef struct Point:
double x
double y

首先,下面的导入被用来引入C函数库和Python C API中定义的函数:

from cpython.pycapsule cimport *  # <---胶囊结构函数库,直接来自Python C API
from libc.stdlib cimport malloc, free

包装如下,先建立结构体,最后以胶囊形式返回:

# Destructor for cleaning up Point objects
cdef del_Point(object obj):
pt = <csample.Point *> PyCapsule_GetPointer(obj,"Point") # <---胶囊结构提取指针(胶囊结构还原结构体)
free(<void *> pt) # Create a Point object and return as a capsule
def Point(double x,double y):
cdef csample.Point *p
p = <csample.Point *> malloc(sizeof(csample.Point))
if p == NULL:
raise MemoryError("No memory to make a Point")
p.x = x
p.y = y
return PyCapsule_New(<void *>p,"Point",<PyCapsule_Destructor>del_Point)

函数 del_Point()Point() 使用这个功能来创建一个胶囊对象, 它会包装一个 Point  * 指针。

cdef  del_Point()del_Point() 声明为一个函数, 只能通过Cython访问,而不能从Python中访问。 因此,这个函数对外部是不可见的——它被用来当做一个回调函数来清理胶囊分配的内存。 函数调用比如 PyCapsule_New()PyCapsule_GetPointer() 直接来自Python C API以同样的方式被使用。

distance 函数从 Point() 创建的胶囊对象中提取指针,

def distance(p1, p2):
pt1 = <csample.Point *> PyCapsule_GetPointer(p1,"Point")
pt2 = <csample.Point *> PyCapsule_GetPointer(p2,"Point")
return csample.distance(pt1,pt2)

这里要注意的是你不需要担心异常处理。 如果一个错误的对象被传进来,PyCapsule_GetPointer() 会抛出一个异常, 但是Cython已经知道怎么查找到它,并将它从 distance() 传递出去。

处理Point结构体一个缺点是它的实现是不可见的。 你不能访问任何属性来查看它的内部。 这里有另外一种方法去包装它,就是定义一个扩展类型,如下所示:

# sample.pyx

cimport csample
from libc.stdlib cimport malloc, free
... cdef class Point:
cdef csample.Point *_c_point # 声明Point结构体
def __cinit__(self, double x, double y): # 初始化过程就是建立一个结构体
self._c_point = <csample.Point *> malloc(sizeof(csample.Point))
self._c_point.x = x
self._c_point.y = y def __dealloc__(self):
free(self._c_point) property x: # 方法修饰为属性
def __get__(self):
return self._c_point.x
def __set__(self, value):
self._c_point.x = value property y: # 方法修饰为属性
def __get__(self):
return self._c_point.y
def __set__(self, value):
self._c_point.y = value def distance(Point p1, Point p2):
return csample.distance(p1._c_point, p2._c_point)

在这里,cdif类 Point 将Point声明为一个扩展类型。 类属性 cdef csample.Point *_c_point 声明了一个实例变量, 拥有一个指向底层Point结构体的指针。 __cinit__()__dealloc__() 方法通过 malloc()free() 创建并销毁底层C结构体。 x和y属性的声明让你获取和设置底层结构体的属性值。 distance() 的包装器还可以被修改,使得它能接受 Point 扩展类型实例作为参数, 而传递底层指针给C函数。

做了这个改变后,你会发现操作Point对象就显得更加自然了:

>>> import sample
>>> p1 = sample.Point(2,3)
>>> p2 = sample.Point(4,5)
>>> p1
<sample.Point object at 0x100447288>
>>> p2
<sample.Point object at 0x1004472a0>
>>> p1.x
2.0
>>> p1.y
3.0
>>> sample.distance(p1,p2)
2.8284271247461903
>>>

setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext ext_modules = [
Extension('sample', ['sample.pyx'],
libraries=['sample'],
library_dirs=['.'])]
setup(
name = 'Sample extension module',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)

编译运行

python setup.py build_ext --inplace

注意,编译完成后sample.c文件就会被修改添加很多语句,所以记得备份。

『Python CoolBook』Cython的更多相关文章

  1. 『Python CoolBook』Cython_高效数组操作

    数组运算加速是至关科学计算重要的领域,本节我们以一个简单函数为例,使用C语言为python数组加速. 一.Cython 本函数为一维数组修剪最大最小值 version1 @cython.boundsc ...

  2. 『Python CoolBook』使用ctypes访问C代码_上_用法讲解

    一.动态库文件生成 源文件hello.c #include "hello.h" #include <stdio.h> void hello(const char *na ...

  3. 『Python CoolBook』使用ctypes访问C代码_下_demo进阶

    点击进入项目 这一次我们尝试一下略微复杂的c程序. 一.C程序 头文件: #ifndef __SAMPLE_H__ #define __SAMPLE_H__ #include <math.h&g ...

  4. 『Python CoolBook』C扩展库_其一_用法讲解

    不依靠其他工具,直接使用Python的扩展API来编写一些简单的C扩展模块. 本篇参考PythonCookbook第15节和Python核心编程完成,值得注意的是,Python2.X和Python3. ...

  5. 『Python CoolBook』C扩展库_其二_demo演示

    点击进入项目 C函数源文件 /* sample.c */ #include "sample.h" /* Compute the greatest common divisor */ ...

  6. 『Python CoolBook』C扩展库_其三_简单数组操作

    点击进入项目 这里的数组要点在于: 数组结构,array.array或者numpy.array 本篇的数组仅限一维,不过基础的C数组也是一维 一.分块讲解 源函数 /* Average values ...

  7. 『Python CoolBook』C扩展库_其四_结构体操作与Capsule

    点击进入项目 一.Python生成C语言结构体 C语言中的结构体传给Python时会被封装为胶囊(Capsule), 我们想要一个如下结构体进行运算,则需要Python传入x.y两个浮点数, type ...

  8. 『Python CoolBook』C扩展库_其五_C语言层面Python库之间调用API

    点击进入项目 一.C层面模块添加API 我们仍然操作如下结构体, #include <math.h> typedef struct Point { double x,y; } Point; ...

  9. 『Python CoolBook』C扩展库_其六_从C语言中调用Python代码

    点击进入项目 一.C语言运行pyfun的PyObject对象 思路是在C语言中提供实参,传给python函数: 获取py函数对象(PyObject),函数参数(C类型) 获取GIL(PyGILStat ...

随机推荐

  1. Linux内核态用户态相关知识 & 相互通信

    http://www.cnblogs.com/bakari/p/5520860.html 内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境. 系统调用是操作系统的最小功能 ...

  2. SpringMVC Maven项目 java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServle

    今天在搭建SpringMVC开发框架时,遇到了一个问题,尽管在maven的POM.xml文件中配置了项目所依赖的jar包,但在启动项目是已然报错如下: 信息: Starting Servlet Eng ...

  3. h5新增标签及css3新增属性

    - h5新增的标签 新增元素 说明 video 表示一段视频并提供播放的用户界面 audio 表示音频 canvas 表示位图区域 source 为video和audio提供数据源 track 为vi ...

  4. Python3学习之路~5.13 re模块 正则表达式

    re模块用于对python的正则表达式的操作. 常用正则表达式符号 字符数字: . 匹配除换行符以外的任意字符,即[^\n] \s 匹配任意空白符(如\t.\n.\r ) \S 匹配任意非空白符 \w ...

  5. 绿色版mssql

    1.安装2008绿色版,缺少对应的企业管理器,安装官方版本的提示电脑没有重启(已经重启后) 2.选择一个可用版本的mssql,2000的可以用,MSSQL2000-HaoSQL,自带企业管理器和查询器

  6. PHP GZ压缩与解压

    /*将字符串添加至GZ文件*/ function gz_str($str,$gz_name){ $fp = gzopen ($gz_name, 'w9'); gzwrite ($fp, $str); ...

  7. Java package can not import alias

    you can not do "import x as y;" in Java. What you CAN do is to extend the class, or write ...

  8. cocos2d-x 编译 安卓(android)apk文件

    摘要: 一.下载Android环境 搭建Android环境需要用到Android SDK.NDK.Ant和JDK: 下载Android SDK  下载Android NDk  下载Android JD ...

  9. Python基础(二)自定义函数

    1.判断字符串,内容是否为数字 我们用python:xlrd读Excel内容时,本来只是输入的整数字,经常读出来的是float类型 我们需要自动转成整型,意思就是说,读出来的和我们输入的一样,但是,我 ...

  10. Python基础(十一) 类继承

    类继承: 继承的想法在于,充份利用已有类的功能,在其基础上来扩展来定义新的类. Parent Class(父类) 与 Child Class(子类): 被继承的类称为父类,继承的类称为子类,一个父类, ...