这个章节包含许多在python代码中支持c/c++本机代码的许多不一样方法, 一般这个过程叫做包裹(wrapping)。本章的目的是让您大体知道有那些技术和它们分别的优缺点是什么,因而您可以为您本身的特定须要选择什么时候的技术。在任何状况下,一旦您开始包裹,您几乎必定将想要查阅您所选技术各自的文档。html
本章节包含如下技术:python
这四种方法大概是最著名的,其中Cython多是最高级且应该优先使用的。若是您想从其它角度理解包裹问题,其它方法也很重要。已经说过,虽然还有其它方法,可是理解以上基本方法,您将能评估您本身的选择看是否符合本身的须要。linux
如下标准在评估一项技术时也许有用:c++
首先,您应该考虑你的用例。当用本机代码接口时,一般有两个用例:git
每一个技术经过包裹math.h
中的cos
函数实现。尽管这是微不足道的例子,它将很好的展现基本的包裹问题。由于每一个技术也包括某种形式的Numpy支持,这也经过使用一个余弦函数被在某种数组上计算的例子来展现。github
最后但重要的是两个小警告:编程
Python-C-API是标准Python解释器(就是所谓的CPython)的支柱。使用这个API能够用C或C++语言编写Python扩展。显然这些扩展模块能够凭借语言兼容性,调用任何C或C++写成的函数。segmentfault
当使用Python-C-API时,人们一般写许多样板代码,先解析传递给函数的参数,而后构建并返回类型。api
优势数组
劣势
注意:如下Python-C-Api示例主要为了展现须要。由于大多其它技术实际上依赖这个,因此最好对它如何工做有个高层次的了解。在99%的用例中你最好使用其它技术。
如下C扩展模块,让标准数学库中的cos
函数在Python中可用:
/* Example of wrapping cos function from math.h with the Python-C-API. */ #include <Python.h> #include <math.h> /* wrapped cosine function */ static PyObject* cos_func(PyObject* self, PyObject* args) { double value; double answer; /* parse the input, from python float to c double */ if (!PyArg_ParseTuple(args, "d", &value)) return NULL; /* if the above function returns -1, an appropriate Python exception will * have been set, and the function simply returns NULL */ /* call cos from libm */ answer = cos(value); /* construct the output from cos, from c double to python float */ return Py_BuildValue("f", answer); } /* define functions in module */ static PyMethodDef CosMethods[] = { {"cos_func", cos_func, METH_VARARGS, "evaluate the cosine"}, {NULL, NULL, 0, NULL} }; /* module initialization */ PyMODINIT_FUNC initcos_module(void) { (void) Py_InitModule("cos_module", CosMethods); }
如您所见,全部对参数处理、返回类型和模块初始化都至关样板化。然而有些被摊销了,当扩展增加时,样板须要每一个函数保留。
标准python构建系统distutils
支持从setup.py
编译C扩展,这至关方便。
from distutils.core import setup, Extension # define the extension module cos_module = Extension('cos_module', sources=['cos_module.c']) # run the setup setup(ext_modules=[cos_module])
这能被编译:
~/Work/scipy-lecture-notes/interfacing-with-c $ls cos_module.c setup.py ~/Work/scipy-lecture-notes/interfacing-with-c $python setup.py build_ext --inplace running build_ext building 'cos_module' extension x86_64-pc-linux-gnu-gcc -pthread -fPIC -I/usr/include/python2.7 -c cos_module.c -o build/temp.linux-x86_64-2.7/cos_module.o x86_64-pc-linux-gnu-gcc -pthread -shared build/temp.linux-x86_64-2.7/cos_module.o -L/usr/lib64 -lpython2.7 -o /home/lyy/Work/scipy-lecture-notes/interfacing-with-c/cos_module.so ~/Work/scipy-lecture-notes/interfacing-with-c $ls build cos_module.c cos_module.so setup.py
build_ext
是用来构建扩展模块的--inplace
将编译好的扩展模块输出到当前文件夹文件cos_module.so
包含编译的扩展,咱们能将它加载到IPython解释器中:
In [1]: import cos_module In [2]: cos_module? Type: module String Form:<module 'cos_module' from 'cos_module.so'> File: /home/lyy/Work/scipy-lecture-notes/interfacing-with-c/cos_module.so Docstring: <no docstring> In [3]: dir(cos_module) Out[3]: ['__doc__', '__file__', '__name__', '__package__', 'cos_func'] In [4]: cos_module.cos_func(1.0) Out[4]: 0.5403023058681398 In [5]: cos_module.cos_func(0.0) Out[5]: 1.0 In [6]: cos_module.cos_func(3.14159265359) Out[6]: -1.0
如今让咱们看看它有多健壮:
In [7]: cos_module.cos_func('foo') --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-11bee483665d> in <module>() ----> 1 cos_module.cos_func('foo') TypeError: a float is required
相似于Python-C-API,Numpu自身做为C扩展实现也有Numpy-C-API。这个API能够在写自定的C扩展时,被用来从C建立和操做Numpy数组。参见高级Numpy
如下例子展现了如何将Numpy数组做为参数传递给函数,如何使用(老的)Numpy-C-API遍历整个Numpy数组。它仅仅将数组做为参数,运用来自math.h
中的余弦函数,而且返回一个新的结果数组。
/* Example of wrapping the cos function from math.h using the Numpy-C-API. */ #include <Python.h> #include <numpy/arrayobject.h> #include <math.h> /* wrapped cosine function */ static PyObject* cos_func_np(PyObject* self, PyObject* args) { PyArrayObject *in_array; PyObject *out_array; PyArrayIterObject *in_iter; PyArrayIterObject *out_iter; /* parse single numpy array argument */ if (!PyArg_ParseTuple(args, "O!", &PyArray_Type, &in_array)) return NULL; /* construct the output array, like the input array */ out_array = PyArray_NewLikeArray(in_array, NPY_ANYORDER, NULL, 0); if (out_array == NULL) return NULL; /* create the iterators */ /* TODO: this iterator API is deprecated since 1.6 * replace in favour of the new NpyIter API */ in_iter = (PyArrayIterObject *)PyArray_IterNew((PyObject*)in_array); out_iter = (PyArrayIterObject *)PyArray_IterNew(out_array); if (in_iter == NULL || out_iter == NULL) goto fail; /* iterate over the arrays */ while (in_iter->index < in_iter->size && out_iter->index < out_iter->size) { /* get the datapointers */ double * in_dataptr = (double *)in_iter->dataptr; double * out_dataptr = (double *)out_iter->dataptr; /* cosine of input into output */ *out_dataptr = cos(*in_dataptr); /* update the iterator */ PyArray_ITER_NEXT(in_iter); PyArray_ITER_NEXT(out_iter); } /* clean up and return the result */ Py_DECREF(in_iter); Py_DECREF(out_iter); Py_INCREF(out_array); return out_array; /* in case bad things happen */ fail: Py_XDECREF(out_array); Py_XDECREF(in_iter); Py_XDECREF(out_iter); return NULL; } /* define functions in module */ static PyMethodDef CosMethods[] = { {"cos_func_np", cos_func_np, METH_VARARGS, "evaluate the cosine on a numpy array"}, {NULL, NULL, 0, NULL} }; /* module initialization */ PyMODINIT_FUNC initcos_module_np(void) { (void) Py_InitModule("cos_module_np", CosMethods); /* IMPORTANT: this must be called */ import_array(); }
咱们仍可以使用distutils编译这个。然而,咱们必须经过使用numpy.get_include()
保证包含了Numpy头文件。
from distutils.core import setup, Extension import numpy # define the extension module cos_module_np = Extension('cos_module_np', sources=['cos_module_np.c'], include_dirs=[numpy.get_include()]) # run the setup setup(ext_modules=[cos_module_np])
为确信它确实能用咱们作如下测试脚本:
import cos_module_np import numpy as np import pylab x = np.arange(0, 2 * np.pi, 0.1) y = cos_module_np.cos_func_np(x) pylab.plot(x, y) pylab.show()
结果将以下图
Ctypes是一个Python的外部函数库。它提供了兼容C的数据类型。而且容许调用DLL或共享库中的函数。它可以被用来将这些库用纯Python包裹。
优点
劣势
*.dll
、Linux下的*.so
和Mac OSX的*.dylib
)如上所述,包裹的代码是纯Python的。
""" Example of wrapping cos function from math.h using ctypes. """ import ctypes from ctypes.util import find_library # find and load the library libm = ctypes.cdll.LoadLibrary(find_library('m')) # set the argument type libm.cos.argtypes = [ctypes.c_double] # set the return type libm.cos.restype = ctypes.c_double def cos_func(arg): ''' Wrapper for cos from math.h ''' return libm.cos(arg)
咱们如今如前述那样使用它:
In [1]: import cos_module In [2]: cos_module? Type: module String Form:<module 'cos_module' from 'cos_module.py'> File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/ctypes/cos_module.py Docstring: <no docstring> In [3]: dir(cos_module) Out[3]: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'cos_func', 'ctypes', 'find_library', 'libm'] In [4]: cos_module.cos_func(1.0) Out[4]: 0.5403023058681398 In [5]: cos_module.cos_func(0.0) Out[5]: 1.0 In [6]: cos_module.cos_func(3.14159265359) Out[6]: -1.0
正如以前的例子,这个代码稍微健壮一些。尽管错误信息不怎么有用,因它并没告诉咱们应该是什么类型。
In [7]: cos_module.cos_func('foo') --------------------------------------------------------------------------- ArgumentError Traceback (most recent call last) <ipython-input-7-11bee483665d> in <module>() ----> 1 cos_module.cos_func('foo') /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/ctypes/cos_module.py in cos_func(arg) 12 def cos_func(arg): 13 ''' Wrapper for cos from math.h ''' ---> 14 return libm.cos(arg) ArgumentError: argument 1: <type 'exceptions.TypeError'>: wrong type
Numpy包含一些对ctypes接口的支持。特别是有导出Numpy数组做为ctypes数据类型的某一属性的支持,而且有将C数组和Numpy数组互相转化的函数。
更多信息参考Numpy Cookbook中相应章节和numpy.ndarray.ctypes和numpy.ctypeslib的API文档。
在如下例子中,让咱们考虑一个库中的C函数,这个函数接受一个数组做为输入并输出一个数组,计算输入数组的正弦值并将结果存储在输出数组中。
这个库包含如下头文件(尽管就这个例子不严格须要,为完整性须要咱们列出它):
void cos_doubles(double * in_array, double * out_array, int size);
这个实如今C源码中以下:
#include <math.h> /* Compute the cosine of each element in in_array, storing the result in * out_array. */ void cos_doubles(double * in_array, double * out_array, int size){ int i; for(i=0;i<size;i++){ out_array[i] = cos(in_array[i]); } }
由于这个库是纯C的,咱们不能使用distutils
来编译它。必须同时使用make
和gcc
:
m.PHONY : clean libcos_doubles.so : cos_doubles.o gcc -shared -Wl,-soname,libcos_doubles.so -o libcos_doubles.so cos_doubles.o cos_doubles.o : cos_doubles.c gcc -c -fPIC cos_doubles.c -o cos_doubles.o clean : -rm -vf libcos_doubles.so cos_doubles.o cos_doubles.pyc
咱们接着能够将之编译到共享库libcos_double.so
中(linux下):
$ ls cos_doubles.c cos_doubles.h cos_doubles.py makefile test_cos_doubles.py $ make gcc -c -fPIC cos_doubles.c -o cos_doubles.o gcc -shared -Wl,-soname,libcos_doubles.so -o libcos_doubles.so cos_doubles.o $ ls cos_doubles.c cos_doubles.o libcos_doubles.so* test_cos_doubles.py cos_doubles.h cos_doubles.py makefile
接着咱们能继续经过ctypes库对(某些类型)Numpy数组的直接支持包裹这个库了:
""" Example of wrapping a C library function that accepts a C double array as input using the numpy.ctypeslib. """ import numpy as np import numpy.ctypeslib as npct from ctypes import c_int # input type for the cos_doubles function # must be a double array, with single dimension that is contiguous array_1d_double = npct.ndpointer(dtype=np.double, ndim=1, flags='CONTIGUOUS') # load the library, using numpy mechanisms libcd = npct.load_library("libcos_doubles", ".") # setup the return typs and argument types libcd.cos_doubles.restype = None libcd.cos_doubles.argtypes = [array_1d_double, array_1d_double, c_int] def cos_doubles_func(in_array, out_array): return libcd.cos_doubles(in_array, out_array, len(in_array))
numpy.zeros()
,这个函数将写进它的缓冲区。cos_doubles
函数的原始参数是ARRAY, ARRAY, int
,最终的cos_doubles_func
仅仅接受两个Numpy数组做为参数。像以前同样,咱们相信它可以工做:
import numpy as np import pylab import cos_doubles x = np.arange(0, 2 * np.pi, 0.1) y = np.empty_like(x) cos_doubles.cos_doubles_func(x, y) pylab.plot(x, y) pylab.show()
SWIG, 简化包裹接口生成器,是一个将不一样高级编程语言包括Python连接到用C和C++写的程序上的软件开发工具。SWIG重要的功能是,它能自动为你生成包裹代码。这就开发时间来讲是个优点,也多是个负担。生成文件趋于巨大,读起来不友好,包裹过程的结果就是多个间接层,可能有点难以理解。
注意:自动生成的C代码使用Python-C-Api。
优点
劣势
让咱们假设咱们的cos
函数位于用C写成的cos_module
中,源代码文件为cos_module.c
。
#include <math.h> double cos_func(double arg){ return cos(arg); }
头文件为cos_module.h
:
double cos_func(double arg);
咱们的任务是将cos_func
暴露给Python。为了用SWIG实现这个,咱们必须写一个包含SWIG指令的接口文件。
/* Example of wrapping cos function from math.h using SWIG. */ %module cos_module %{ /* the resulting C file should be built as a python extension */ #define SWIG_FILE_WITH_INIT /* Includes the header in the wrapper code */ #include "cos_module.h" %} /* Parse the header file to generate wrappers */ %include "cos_module.h"
如您所见,须要太多代码了。在这个简单的例子中在接口文件中仅仅包含头文件就足够将函数暴露给Python。然而,SWIG容许更细粒度地包含/排除头文件中的函数,查看文档获取更多细节。
产生编译的包裹代码是一个两个阶段的过程:
cos_module_wrap.c
,这是用来自动生成Python的C扩展的源代码文件。cos_module.py
是自动生成的纯Python模块。cos_module_wrap.c
为_cos_module.so
。幸运的是,distutils
知道如何处理SWIG接口文件,因此咱们的setup.py
很简单:from distutils.core import setup, Extension setup(ext_modules=[Extension("_cos_module", sources=["cos_module.c", "cos_module.i"])]) $ cd advanced/interfacing_with_c/swig $ ls cos_module.c cos_module.h cos_module.i setup.py $ python setup.py build_ext --inplace running build_ext building '_cos_module' extension swigging cos_module.i to cos_module_wrap.c swig -python -o cos_module_wrap.c cos_module.i creating build creating build/temp.linux-x86_64-2.7 gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module.c -o build/temp.linux-x86_64-2.7/cos_module.o gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module_wrap.c -o build/temp.linux-x86_64-2.7/cos_module_wrap.o gcc -pthread -shared build/temp.linux-x86_64-2.7/cos_module.o build/temp.linux-x86_64-2.7/cos_module_wrap.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig/_cos_module.so $ ls build/ cos_module.c cos_module.h cos_module.i cos_module.py _cos_module.so* cos_module_wrap.c setup.py
如今咱们能加载和执行cos_module
,就好像咱们以前作的:
In [1]: import cos_module In [2]: cos_module? Type: module String Form:<module 'cos_module' from 'cos_module.py'> File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig/cos_module.py Docstring: <no docstring> In [3]: dir(cos_module) Out[3]: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_cos_module', '_newclass', '_object', '_swig_getattr', '_swig_property', '_swig_repr', '_swig_setattr', '_swig_setattr_nondynamic', 'cos_func'] In [4]: cos_module.cos_func(1.0) Out[4]: 0.5403023058681398 In [5]: cos_module.cos_func(0.0) Out[5]: 1.0 In [6]: cos_module.cos_func(3.14159265359) Out[6]: -1.0
咱们再次检验健壮性,看到获得了更好的错误信息(然而,严格地说Python中没有double
类型):
In [7]: cos_module.cos_func('foo') --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-11bee483665d> in <module>() ----> 1 cos_module.cos_func('foo') TypeError: in method 'cos_func', argument 1 of type 'double'
numpy经过numpy.i
文件提供了对SWIG的支持。这个接口文件定义了各类所谓的类型映射(typemaps)来转换Numpy数组和C数组。在下面的例子中咱们将简略地看看这种类型映射在实际中如何起做用。
咱们使用在ctypes例子中相同的cos_doubles
函数:
void cos_doubles(double * in_array, double * out_array, int size); #include <math.h> /* Compute the cosine of each element in in_array, storing the result in * out_array. */ void cos_doubles(double * in_array, double * out_array, int size){ int i; for(i=0;i<size;i++){ out_array[i] = cos(in_array[i]); } }
使用SWIG接口文件将它包裹为cos_doubles_func
:
/* Example of wrapping a C function that takes a C double array as input using * numpy typemaps for SWIG. */ %module cos_doubles %{ /* the resulting C file should be built as a python extension */ #define SWIG_FILE_WITH_INIT /* Includes the header in the wrapper code */ #include "cos_doubles.h" %} /* include the numpy typemaps */ %include "numpy.i" /* need this for correct module initialization */ %init %{ import_array(); %} /* typemaps for the two arrays, the second will be modified in-place */ %apply (double* IN_ARRAY1, int DIM1) {(double * in_array, int size_in)} %apply (double* INPLACE_ARRAY1, int DIM1) {(double * out_array, int size_out)} /* Wrapper for cos_doubles that massages the types */ %inline %{ /* takes as input two numpy arrays */ void cos_doubles_func(double * in_array, int size_in, double * out_array, int size_out) { /* calls the original funcion, providing only the size of the first */ cos_doubles(in_array, out_array, size_in); } %}
numpy.i
文件。import_array()
的调用,咱们已经在Numpy-C-Api的例子中见到过。ARRAY, SIZE
咱们须要包裹cos_doubles
为cos_doubles_func
,该函数接受两个数组包含各自大小做为输入。cos_doubles.h
头文件,由于咱们经过cos_doubles_func
暴露这个功能,咱们没有其它东西想暴露给Python。而后,如前述用distutils包裹它:
from distutils.core import setup, Extension import numpy setup(ext_modules=[Extension("_cos_doubles", sources=["cos_doubles.c", "cos_doubles.i"], include_dirs=[numpy.get_include()])])
显然,咱们须要include_dirs
指定位置。
$ ls cos_doubles.c cos_doubles.h cos_doubles.i numpy.i setup.py test_cos_doubles.py $ python setup.py build_ext -i running build_ext building '_cos_doubles' extension swigging cos_doubles.i to cos_doubles_wrap.c swig -python -o cos_doubles_wrap.c cos_doubles.i cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found. cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found. cos_doubles.i:24: Warning(490): Fragment 'NumPy_Backward_Compatibility' not found. creating build creating build/temp.linux-x86_64-2.7 gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c cos_doubles.c -o build/temp.linux-x86_64-2.7/cos_doubles.o gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c cos_doubles_wrap.c -o build/temp.linux-x86_64-2.7/cos_doubles_wrap.o In file included from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1722, from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:17, from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:15, from cos_doubles_wrap.c:2706: /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/npy_deprecated_api.h:11:2: warning: #warning "Using deprecated NumPy API, disable it by #defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" gcc -pthread -shared build/temp.linux-x86_64-2.7/cos_doubles.o build/temp.linux-x86_64-2.7/cos_doubles_wrap.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/swig_numpy/_cos_doubles.so $ ls build/ cos_doubles.h cos_doubles.py cos_doubles_wrap.c setup.py cos_doubles.c cos_doubles.i _cos_doubles.so* numpy.i test_cos_doubles.py
接着,确信它起做用:
import numpy as np import pylab import cos_doubles x = np.arange(0, 2 * np.pi, 0.1) y = np.empty_like(x) cos_doubles.cos_doubles_func(x, y) pylab.plot(x, y) pylab.show()
Cython不只使用来写C扩展的Python样子的语言,并且是这个语言的一个高级编译器。Cython语言是Python的超集,包含额外的结构容许你调用C函数,将变量和类属性解释为C类型。在这个意义上能够叫它Python的一个类型。
除了这些几本的包裹原生代码的用例,Cython支持一个额外的用例,即交互优化。基本上是,从纯Python代码脚本出发逐步向代码瓶颈增长Cython类型来优化那些真正值得优化的代码。
在这个意义上它和SWIG很是类似,由于C代码能够自动生成,但某种意义上它也至关相似与ctypes,由于它包裹代码能够(几乎能够)用Python写成。
尽管其它自动生成代码方案会很难调试(例如SWIG),Cython带有一个GNU调试器的扩展,能帮助调试Python,Cython和C代码。
注意:自动生成的C代码使用了Python-C-Api。
优点
劣势
咱们cos_module
的主要的Cython代码包含在文件cos_module.pyx
中:
""" Example of wrapping cos function from math.h using Cython. """ cdef extern from "math.h": double cos(double arg) def cos_func(arg): return cos(arg)
注意额外的关键字像cdef
和extern
。cos_func
紧接着是纯Python。
咱们再次使用标准distutils
模块,可是此次咱们须要一些来自Cython.Distutils
额外的片断:
from distutils.core import setup, Extension from Cython.Distutils import build_ext setup( cmdclass={'build_ext': build_ext}, ext_modules=[Extension("cos_module", ["cos_module.pyx"])] )
编译它:
$ cd advanced/interfacing_with_c/cython $ ls cos_module.pyx setup.py $ python setup.py build_ext --inplace running build_ext cythoning cos_module.pyx to cos_module.c building 'cos_module' extension creating build creating build/temp.linux-x86_64-2.7 gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module.c -o build/temp.linux-x86_64-2.7/cos_module.o gcc -pthread -shared build/temp.linux-x86_64-2.7/cos_module.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so $ ls build/ cos_module.c cos_module.pyx cos_module.so* setup.py
而后运行它:
In [1]: import cos_module In [2]: cos_module? Type: module String Form:<module 'cos_module' from 'cos_module.so'> File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so Docstring: <no docstring> In [3]: dir(cos_module) Out[3]: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__test__', 'cos_func'] In [4]: cos_module.cos_func(1.0) Out[4]: 0.5403023058681398 In [5]: cos_module.cos_func(0.0) Out[5]: 1.0 In [6]: cos_module.cos_func(3.14159265359) Out[6]: -1.0
接着,测试健壮性,能够看到咱们得到了很棒的错误信息:
In [7]: cos_module.cos_func('foo') --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-11bee483665d> in <module>() ----> 1 cos_module.cos_func('foo') /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so in cos_module.cos_func (cos_module.c:506)() TypeError: a float is required
另外,值得注意的是Cython带有完整的C数学库声明,将之上代码简化为:
""" Simpler example of wrapping cos function from math.h using Cython. """ from libc.math cimport cos def cos_func(arg): return cos(arg)
在这个例子中cimport
声明被用来importcos
函数。
Cython经过numpy.pyx
文件支持Numpy,这容许你将Numpy数组类型添加到Cython代码。例如将i
指定为int
类型,将变量a
指定为numpy.ndarray
并给定dtype
。某些优化像边界检查也支持。参看Cython文档的相关章节。万一你想将Numpy数组做为C数组传递给你的Cython包裹的C代码,Cython维基中有一个章节。
在如下例子中,咱们将展现如何如何使用Cython包裹熟悉的cos_doubles
函数。
void cos_doubles(double * in_array, double * out_array, int size); #include <math.h> /* Compute the cosine of each element in in_array, storing the result in * out_array. */ void cos_doubles(double * in_array, double * out_array, int size){ int i; for(i=0;i<size;i++){ out_array[i] = cos(in_array[i]); } }
该函数使用如下Cython代码被包裹为cos_doubles_func
:
""" Example of wrapping a C function that takes C double arrays as input using the Numpy declarations from Cython """ # import both numpy and the Cython declarations for numpy import numpy as np cimport numpy as np # if you want to use the Numpy-C-API from Cython # (not strictly necessary for this example) np.import_array() # cdefine the signature of our c function cdef extern from "cos_doubles.h": void cos_doubles (double * in_array, double * out_array, int size) # create the wrapper code, with numpy type annotations def cos_doubles_func(np.ndarray[double, ndim=1, mode="c"] in_array not None, np.ndarray[double, ndim=1, mode="c"] out_array not None): cos_doubles(<double*> np.PyArray_DATA(in_array), <double*> np.PyArray_DATA(out_array), in_array.shape[0])
可使用distutils
编译:
from distutils.core import setup, Extension import numpy from Cython.Distutils import build_ext setup( cmdclass={'build_ext': build_ext}, ext_modules=[Extension("cos_doubles", sources=["_cos_doubles.pyx", "cos_doubles.c"], include_dirs=[numpy.get_include()])], )
include_dirs
选项。$ ls cos_doubles.c cos_doubles.h _cos_doubles.pyx setup.py test_cos_doubles.py $ python setup.py build_ext -i running build_ext cythoning _cos_doubles.pyx to _cos_doubles.c building 'cos_doubles' extension creating build creating build/temp.linux-x86_64-2.7 gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c _cos_doubles.c -o build/temp.linux-x86_64-2.7/_cos_doubles.o In file included from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1722, from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:17, from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:15, from _cos_doubles.c:253: /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/npy_deprecated_api.h:11:2: warning: #warning "Using deprecated NumPy API, disable it by #defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/__ufunc_api.h:236: warning: ‘_import_umath’ defined but not used gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c cos_doubles.c -o build/temp.linux-x86_64-2.7/cos_doubles.o gcc -pthread -shared build/temp.linux-x86_64-2.7/_cos_doubles.o build/temp.linux-x86_64-2.7/cos_doubles.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython_numpy/cos_doubles.so $ ls build/ _cos_doubles.c cos_doubles.c cos_doubles.h _cos_doubles.pyx cos_doubles.so* setup.py test_cos_doubles.py
如前述确保它能起做用:
import numpy as np import pylab import cos_doubles x = np.arange(0, 2 * np.pi, 0.1) y = np.empty_like(x) cos_doubles.cos_doubles_func(x, y) pylab.plot(x, y) pylab.show()
这个章节中四种不一样和本地代码接口技术被呈如今您面前。这个表格简要的总结了这些技术的某些方面。
x Part of CPython Compiled Autogenerated Numpy Support -------------- ----------------- ---------- --------------- --------------- Python-C-Api True True False True Ctypes True False False True Swig False True True True Cython False True True True
相比全部技术中,Cython是最现代最高级的了。特别是,经过向Python代码中添加类型增量优化代码的能力是独一无二的。
因这是一个全新的章节,这些练习更可视为是接下来看什么的指针。因此选择您最感兴趣的那个。若是你对此有更多好的想法,请联系咱们!
cos
改为sin
)大多数例子,特别是涉及Numpy的例子可能仍然对错误输入很脆弱、而且返回模糊的消息。寻找使这些例子出问题的方法,指出问题是什么而且设计潜在的解决方案。这有一些提示:
double
型数组使用IPython中的magic%timeit
来测量不一样方案的执行时间。
cos_double
可是直接将cos
应用到Numpy数组的元素的Numpy例子。这相对于其它技术有什么优点?cos_doubles
吗?你可能须要确保数组是正确的类型,而且在内存中一维连续。cos_double_func
的Numpy例子为你处理预分配,使之更像Numpy-C-Api例子。cos_double_func
处理预分配,让它更像Numpy-C-API的例子。cos_doubles
让它返回一个分配的数组。你能用SWIG类型映射包裹它?若是不能,为什么不行?有没有特定条件的变通方案。(提示:你知道输出数组的大小,因此可能从返回的double *
构建一个Numpy数组。)cos_doubles_func
处理预分配,使之更像Numpy-C-Api例子。原文 scipy lecture notes: Interfacing with C
翻译 reverland under GNU Free Documentation License 1.2.