這個章節包含許多在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.