用Cython简单的包装C代码

我有一些C函数,我想从Python中调用它们。 cython似乎是要走的路,但我不能真正find一个这样做的例子。 我的C函数看起来像这样:

void calculate_daily ( char *db_name, int grid_id, int year, double *dtmp, double *dtmn, double *dtmx, double *dprec, double *ddtr, double *dayl, double *dpet, double *dpar ) ; 

我只想指定前三个参数(一个string和两个整数),然后恢复8个numpy数组(或python列表,所有双数组都有N个元素)。 我的代码假定指针指向已经分配的内存块。 另外,生成的C代码应该链接到一些外部库。

下面是一个很小但完整的例子,在逻辑上将numpy数组传递给外部C函数

 fc( int N, double* a, double* b, double* z ) # z = a + b 

使用Cython。 (对于那些了解它的人来说,这一点是非常有名的,欢迎评论。最后更改:2011年2月23日,对于Cython 0.14。)

首先阅读或浏览Cython与NumPy的 构build和Cython 。

2个步骤:

  • python f-setup.py build_ext --inplace
    变成f.pyxfc.cpp – > f.so ,一个dynamic库
  • python test-f.py
    import f加载f.so ; f.fpy( ... )调用C fc( ... )

python f-setup.py使用distutils来运行cython,编译和链接:
cython f.pyx -> f.cpp
编译f.cppfc.cpp
链接到fo fc.o – > f.so ,一个dynamic库,Python import f将加载。

对于学生,我build议:制作这些步骤的图表,查看下面的文件,然后下载并运行它们。

distutils是一个庞大的,令人费解的包,用于制作Python包以进行分发,并安装它们,这里我们仅使用它的一小部分来编译和链接到f.so ,这一步与Cython没有任何关系,但它可能会令人困惑; .pyx中的简单错误会导致来自g ++编译和链接的模糊错误消息的页面,另请参阅distutils doc和distutils上的 SO问题 。

makesetup.py将重新运行cython f.pyxg++ -c ... f.cpp如果f.pyxf.cpp新。
要清理, rm -r build/

setup.py的替代方法是在脚本或Makefile中单独运行这些步骤:
cython --cplus f.pyx -> f.cpp # see cython -h
g++ -c ... f.cpp -> fo
g++ -c ... fc.cpp -> fc.o
cc-lib fo fc.o -> dynamic library f.so
为你的平台和安装修改下面的cc-lib-mac包装:这不是很好,但很小。

有关Cython封装C的真实示例,请查看任何SciKit中的 .pyx文件。

另请参阅: Cython for NumPy用户和SO问题/标记/ cython 。


为了解压下面的文件,把这个文件剪切粘贴到一个大文件中,比如说cython-numpy-c-demo ,然后在Unix中(在一个干净的新目录中)运行sh cython-numpy-c-demo

 #-------------------------------------------------------------------------------- cat >f.pyx <<\! # f.pyx: numpy arrays -> extern from "fc.h" # 3 steps: # cython f.pyx -> fc # link: python f-setup.py build_ext --inplace -> f.so, a dynamic library # py test-f.py: import f gets f.so, f.fpy below calls fc() import numpy as np cimport numpy as np cdef extern from "fc.h": int fc( int N, double* a, double* b, double* z ) # z = a + b def fpy( N, np.ndarray[np.double_t,ndim=1] A, np.ndarray[np.double_t,ndim=1] B, np.ndarray[np.double_t,ndim=1] Z ): """ wrap np arrays to fc( a.data ... ) """ assert N <= len(A) == len(B) == len(Z) fcret = fc( N, <double*> A.data, <double*> B.data, <double*> Z.data ) # fcret = fc( N, A.data, B.data, Z.data ) grr char* return fcret ! #-------------------------------------------------------------------------------- cat >fc.h <<\! // fc.h: numpy arrays from cython , double* int fc( int N, const double a[], const double b[], double z[] ); ! #-------------------------------------------------------------------------------- cat >fc.cpp <<\! // fc.cpp: z = a + b, numpy arrays from cython #include "fc.h" #include <stdio.h> int fc( int N, const double a[], const double b[], double z[] ) { printf( "fc: N=%da[0]=%fb[0]=%f \n", N, a[0], b[0] ); for( int j = 0; j < N; j ++ ){ z[j] = a[j] + b[j]; } return N; } ! #-------------------------------------------------------------------------------- cat >f-setup.py <<\! # python f-setup.py build_ext --inplace # cython f.pyx -> f.cpp # g++ -c f.cpp -> fo # g++ -c fc.cpp -> fc.o # link fo fc.o -> f.so # distutils uses the Makefile distutils.sysconfig.get_makefile_filename() # for compiling and linking: a sea of options. # http://docs.python.org/distutils/introduction.html # http://docs.python.org/distutils/apiref.html 20 pages ... # https://stackoverflow.com/questions/tagged/distutils+python import numpy from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext # from Cython.Build import cythonize ext_modules = [Extension( name="f", sources=["f.pyx", "fc.cpp"], # extra_objects=["fc.o"], # if you compile fc.cpp separately include_dirs = [numpy.get_include()], # .../site-packages/numpy/core/include language="c++", # libraries= # extra_compile_args = "...".split(), # extra_link_args = "...".split() )] setup( name = 'f', cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules, # ext_modules = cythonize(ext_modules) ? not in 0.14.1 # version= # description= # author= # author_email= ) # test: import f ! #-------------------------------------------------------------------------------- cat >test-f.py <<\! #!/usr/bin/env python # test-f.py import numpy as np import f # loads f.so from cc-lib: f.pyx -> fc + fc.o -> f.so N = 3 a = np.arange( N, dtype=np.float64 ) b = np.arange( N, dtype=np.float64 ) z = np.ones( N, dtype=np.float64 ) * np.NaN fret = f.fpy( N, a, b, z ) print "fpy -> fc z:", z ! #-------------------------------------------------------------------------------- cat >cc-lib-mac <<\! #!/bin/sh me=${0##*/} case $1 in "" ) set -- f.cpp fc.cpp ;; # default: g++ these -h* | --h* ) echo " $me [g++ flags] xx.c yy.cpp zz.o ... compiles .c .cpp .o files to a dynamic lib xx.so " exit 1 esac # Logically this is simple, compile and link, # but platform-dependent, layers upon layers, gloom, doom base=${1%.c*} base=${base%.o} set -x g++ -dynamic -arch ppc \ -bundle -undefined dynamic_lookup \ -fno-strict-aliasing -fPIC -fno-common -DNDEBUG `# -g` -fwrapv \ -isysroot /Developer/SDKs/MacOSX10.4u.sdk \ -I/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 \ -I${Pysite?}/numpy/core/include \ -O2 -Wall \ "$@" \ -o $base.so # undefs: nm -gpv $base.so | egrep '^ *U _+[^P]' ! # 23 Feb 2011 13:38 

以下来自http://article.gmane.org/gmane.comp.python.cython.user/5625的; Cython代码不需要显式转换,也可以处理非连续数组:

 def fpy(A): cdef np.ndarray[np.double_t, ndim=2, mode="c"] A_c A_c = np.ascontiguousarray(A, dtype=np.double) fc(&A_c[0,0]) 

基本上,你可以编写你的Cython函数,以便它分配数组(确保你cimport numpy as np ):

 cdef np.ndarray[np.double_t, ndim=1] rr = np.zeros((N,), dtype=np.double) 

然后将每个.data指针传递给你的C函数。 这应该工作。 如果你不需要从零开始,你可以使用np.empty来进行小的速度提升。

请参阅文档中的Cython for NumPy用户教程(将其固定到正确的链接)。

你应该检查出Ctypes这可能是最简单的东西,如果你想要的只是一个function。