# 2.8 與C進行交互
In?[1]:
```
%matplotlib inline
import numpy as np
```
> **作者**: Valentin Haenel
本章包含了許多可以在Python使用原生代碼(主要是C/C++)方式的_介紹_,這個過程通常被稱為_封裝_。本章的目的是給你有哪些技術存在已經各自有優劣式的一點兒感覺,這樣你可以根據你的具體需求選擇適合的方式。無論如何,只要你開始做封裝,你幾乎都必然需要咨詢你選定技術的文檔。
章節內容
```
簡介
Python-C-Api
Ctypes
SWIG
Cython
總結
進一步閱讀和參考
練習
```
## 2.8.1 簡介
本章將涵蓋一下技術:
* Python-C-Api
* Ctypes
* SWIG (簡化封裝器和接口生成器)
* Cython
這四種技術可能是最知名的,其中Cython可能是最高級的,并且你應該最優先使用它。其他的技術也很重要,如果你想要從不同點角度理解封裝問題。之前提到過,還有其他的替代技術,但是,理解了以上這些基礎的,你就可以評估你選擇的技術是否滿足你的需求。
在評估技術時,下列標準會有幫助:
* 需要額外的庫嗎?
* 代碼可以自動生成?
* 是否需要編譯?
* 與Numpy數組交互是否有良好的支持?
* 是否支持C++?
在你動手前,應該先考慮一下使用情景。在于原生代碼交互時,通常來自于兩個應用場景:
* 需要利用C/C++中現存的代碼,或者是因為它已經存在,或者是因為它更快。
* Python代碼太慢,將內部循環變成原生代碼
每個技術都使用來自math.h的`cos`函數的封裝來進行演示。盡管這是一個無聊例子,但是它確實給我們很好的演示了封裝方法的基礎,因為每個技術也包含了一定程度Numpy支持,這也用計算一些數組來計算consine的例子來演示。
最后,兩個小警示:
* 所有這些方法在Python解釋器中都可能崩潰(細分錯誤),因為在C代碼中的錯誤。
* 所有的例子都在Linux中完成,他們應該在其他操作系統中也可用。
* 在大多數例子中,你都會需要C編譯器。
## 2.8.2 Python-C-Api
Python-C-API是標準Python解釋器(即CPython)的基礎。使用這個API可以在C和C++中寫Python擴展模塊。很明顯,由于語言兼容性的優點,這些擴展模塊可以調用任何用C或者C++寫的函數。
當使用Python-C-API時,人們通常寫許多樣板化的代碼,首先解析函數接收的參數,然后構建返回的類型。
**優點**
* 不需要額外的庫
* 許多系層的控制
* C++完全可用
**不足**
* 可以需要一定的努力
* 高代碼成本
* 必須編譯
* 高維護成本
* 如果C-API改變無法向前兼容Python版本
* 引用計數錯誤很容易出現,但是很難被跟蹤。
**注意** 此處的Python-C-Api例子主要是用來演示。許多其他例子的確依賴它,因此,對于它如何工作有一個高層次的理解。在99%的使用場景下,使用替代技術會更好。
**注意** 因為引用計數很容易出現然而很難被跟蹤,任何需要使用Python C-API的人都應該閱讀[官方Python文檔關于對象、類型和引用計數的部分](https://docs.python.org/2/c-api/intro.html#objects-types-and-reference-counts)。此外,有一個名為[cpychecker](https://gcc-python-plugin.readthedocs.org/en/latest/cpychecker.html)的工具可以發現引用計數的常見錯誤。
### 2.8.2.1 例子
下面的C擴展模塊,讓來自標準`math`庫的`cos`函數在Python中可用:
In?[?]:
```
/* 用Python-C-API封裝來自math.h的cos函數的例子 */
#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);
}
```
如你所見,有許多樣板,既包括 ?massage? 的參數和return類型以及模塊初始化。盡管隨著擴展的增長,這些東西中的一些是分期償還,模板每個函數需要的模板還是一樣的。
標準python構建系統`distutils`支持從`setup.py`編譯C-擴展, 非常方便:
In?[?]:
```
from distutils.core import setup, Extension
# 定義擴展模塊
cos_module = Extension('cos_module', sources=['cos_module.c'])
# 運行setup
setup(ext_modules=[cos_module])
```
這可以被編譯:
```
$ cd advanced/interfacing_with_c/python_c_api
$ ls
cos_module.c setup.py
$ python setup.py build_ext --inplace
running build_ext
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/python_c_api/cos_module.so
$ ls
build/ cos_module.c cos_module.so setup.py
```
* `build_ext` 是構建擴展模塊
* `--inplace` 將把編譯后的擴展模塊輸出到當前目錄
文件`cos_module.so`包含編譯后的擴展,我們可以在IPython解釋器中加載它:
In?[?]:
```
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/python_c_api/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[7]: -1.0
```
現在我們看一下這有多強壯:
In?[?]:
```
In [10]: cos_module.cos_func('foo')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-11bee483665d> in <module>()
----> 1 cos_module.cos_func('foo')
TypeError: a float is required
```
### 2.8.2.2\. Numpy 支持
Numpy模擬Python-C-API, 自身也實現了C-擴展, 產生了Numpy-C-API。這個API可以被用來創建和操作來自C的Numpy數組, 當寫一個自定義的C-擴展。也可以看一下:參考:`advanced_numpy`。
**注意** 如果你確實需要使用Numpy C-API參考關于[Arrays](http://docs.scipy.org/doc/numpy/reference/c-api.array.html)和[Iterators](http://docs.scipy.org/doc/numpy/reference/c-api.iterator.html)的文檔。
下列的例子顯示如何將Numpy數組作為參數傳遞給函數,以及如果使用(舊)Numpy-C-API在Numpy數組上迭代。它只是將一個數組作為參數應用到來自`math.h`的cosine函數,并且返回生成的新數組。
In?[?]:
```
/* 使用Numpy-C-API封裝來自math.h的cos函數 . */
#include <Python.h>
#include <numpy/arrayobject.h>
#include <math.h>
/* 封裝cosine函數 */
static PyObject* cos_func_np(PyObject* self, PyObject* args)
{
PyArrayObject *in_array;
PyObject *out_array;
NpyIter *in_iter;
NpyIter *out_iter;
NpyIter_IterNextFunc *in_iternext;
NpyIter_IterNextFunc *out_iternext;
/* 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 */
in_iter = NpyIter_New(in_array, NPY_ITER_READONLY, NPY_KEEPORDER,
NPY_NO_CASTING, NULL);
if (in_iter == NULL)
goto fail;
out_iter = NpyIter_New((PyArrayObject *)out_array, NPY_ITER_READWRITE,
NPY_KEEPORDER, NPY_NO_CASTING, NULL);
if (out_iter == NULL) {
NpyIter_Deallocate(in_iter);
goto fail;
}
in_iternext = NpyIter_GetIterNext(in_iter, NULL);
out_iternext = NpyIter_GetIterNext(out_iter, NULL);
if (in_iternext == NULL || out_iternext == NULL) {
NpyIter_Deallocate(in_iter);
NpyIter_Deallocate(out_iter);
goto fail;
}
double ** in_dataptr = (double **) NpyIter_GetDataPtrArray(in_iter);
double ** out_dataptr = (double **) NpyIter_GetDataPtrArray(out_iter);
/* iterate over the arrays */
do {
**out_dataptr = cos(**in_dataptr);
} while(in_iternext(in_iter) && out_iternext(out_iter));
/* clean up and return the result */
NpyIter_Deallocate(in_iter);
NpyIter_Deallocate(out_iter);
Py_INCREF(out_array);
return out_array;
/* in case bad things happen */
fail:
Py_XDECREF(out_array);
return NULL;
}
/* 在模塊中定義函數 */
static PyMethodDef CosMethods[] =
{
{"cos_func_np", cos_func_np, METH_VARARGS,
"evaluate the cosine on a numpy array"},
{NULL, NULL, 0, NULL}
};
/* 模塊初始化 */
PyMODINIT_FUNC
initcos_module_np(void)
{
(void) Py_InitModule("cos_module_np", CosMethods);
/* IMPORTANT: this must be called */
import_array();
}
```
要編譯這個模塊,我們可以再用`distutils`。但是我們需要通過使用func:numpy.get_include確保包含了Numpy頭部:
In?[?]:
```
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])
```
要說服我們自己這個方式確實有效,我們來跑一下下面的測試腳本:
In?[?]:
```
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()
```
這會產生以下的圖像:

## 2.8.3\. Ctypes
Ctypes是Python的一個外來函數庫。它提供了C兼容的數據類型,并且允許在DLLs或者共享的庫中調用函數。它可以用來在純Python中封裝這些庫。
**優點**
* Python標準庫的一部分
* 不需要編譯
* 代碼封裝都是在Python中
**不足**
* 需要代碼作為一個共享的庫(粗略地說,在windows中是 *.dll,在Linux中是*.so,在Mac OSX中是 *.dylib)
* 對C++支持并不好
### 2.8.3.1 例子
如前面提到的,代碼封裝完全在Python中。
In?[?]:
```
""" 用ctypes封裝來自math.h的 cos 函數。 """
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):
''' 封裝math.h cos函數 '''
return libm.cos(arg)
```
* 尋找和加載庫可能非常依賴于你的操作系統,檢查[文檔](https://docs.python.org/2/library/ctypes.html#loading-dynamic-link-libraries)來了解細節
* 這可能有些欺騙性,因為math庫在系統中已經是編譯模式。如果你想要封裝一個內置的庫,需要先編譯它,可能需要或者不需要額外的工作。 我們現在可以像前面一樣使用這個庫:
In?[?]:
```
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
```
### 2.8.3.2 Numpy支持
Numpy包含一些與ctypes交互的支持。特別是支持將特定Numpy數組屬性作為ctypes數據類型研究,并且有函數可以將C數組和Numpy數據互相轉換。
更多信息,可以看一下Numpy手冊的對應部分或者`numpy.ndarray.ctypes`和`numpy.ctypeslib`的API文檔。
對于下面的例子,讓我們假設一個C函數,輸入輸出都是一個數組,計算輸入數組的cosine并將結果輸出為一個數組。
庫包含下列頭文件(盡管在這個例子中并不是必須這樣,為了完整,我們還是把這一步列出來):
In?[?]:
```
void cos_doubles(double * in_array, double * out_array, int size);
```
這個函數實現在下列的C源文件中:
In?[?]:
```
#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`的組合:
In?[?]:
```
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
```
接下來,我們可以將這個庫編譯到共享的庫 (on Linux)`libcos_doubles.so`:
In?[?]:
```
$ 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數組的直接支持(一定程度上)來封裝這個庫:
In?[?]:
```
""" 封裝一個使用numpy.ctypeslib接受C雙數組作為輸入的例子。"""
import numpy as np
import numpy.ctypeslib as npct
from ctypes import c_int
# cos_doubles的輸入類型
# 必須是雙數組, 有相鄰的單維度
array_1d_double = npct.ndpointer(dtype=np.double, ndim=1, flags='CONTIGUOUS')
# 加載庫,運用numpy機制
libcd = npct.load_library("libcos_doubles", ".")
# 設置反饋類型和參數類型
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數組的固有限制,因為C函數需要這類的緩存器。
* 也需要注意輸出數組也需要是預分配的,例如[numpy.zeros()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.zeros.html#numpy.zeros)和函數將用它的緩存器來寫。
* 盡管`cos_doubles`函數的原始簽名是`ARRAY`, `ARRAY`, `int`最終的`cos_doubles_func`需要兩個Numpy數組作為參數。
并且,和前面一樣,我們我要為自己證明一下它是有效的:
In?[?]:
```
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()
```

## 2.8.4 SWIG
[SWIG](http://www.swig.org/), 簡化封裝接口生成器,是一個聯接用C和C++寫的程序與需要高級程序語言,包括Python的軟件開發工具。SWIG的重點在于它可以為你自動生成封裝器代碼。盡管從編程時間上來說這是一個優勢,但是同時也是一個負擔。生成的文件通常很大,并且可能并不是人類可讀的,封裝過程造成的多層間接引用可能很難理解。
**注意** 自動生成的C代碼使用Python-C-Api。
**優勢**
* 給定頭部可以自動封裝整個庫
* 在C++中表現良好
**不足**
* 自動生成的文件很龐大
* 如果出錯很難debug
* 陡峭的學習曲線
### 2.8.4.1 例子
讓我們想象我們的`cos`函數存在于用C寫的`cos_module`中,包含在源文件`cos_module.c`中:
In?[?]:
```
#include <math.h>
double cos_func(double arg){
return cos(arg);
}
```
頭文件`cos_module.h`:
In?[?]:
```
double cos_func(double arg);
```
盡管我們的目的是將`cos_func`暴露給Python。要用SWIG來完成這個目的,我們需要寫一個包含SWIG指導的接口文件。
In?[?]:
```
/* 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確實允許更多精細包含或者排除在頭文件中發現的函數,細節檢查一下文檔。
生成編譯的封裝器是一個兩階段的過程:
* 在接口文件上運行`swig`可執行文件來生成文件`cos_module_wrap.c`, 其源文件是自動生成的Python C-extension和`cos_module.py`, 是自動生成的Python模塊。
* 編譯`cos_module_wrap.c`到`_cos_module.so`。幸運的,`distutils`知道如何處理SWIG接口文件, 因此我們的`setup.py`是很簡單的:
In?[?]:
```
from distutils.core import setup, Extension
setup(ext_modules=[Extension("_cos_module",
sources=["cos_module.c", "cos_module.i"])])
```
In?[?]:
```
$ 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?[?]:
```
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?[?]:
```
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'
```
### 2.8.4.2 Numpy 支持
Numpy在`numpy.i`文件中提供了[SWIG的支持](http://docs.scipy.org/doc/numpy/reference/swig.html)。這個接口文件定義了許多所謂的typemaps,支持了Numpy數組和C-Arrays的轉化。在接下來的例子中,我們將快速看一下typemaps實際是如何工作的。
我們有相同的`cos_doubles`函數,在ctypes例子中:
In?[?]:
```
void cos_doubles(double * in_array, double * out_array, int size);
```
In?[?]:
```
#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`:
In?[?]:
```
/* 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的typemaps, 我們需要包含`numpy.i`文件。
* 觀察一下對`import_array()`的調用,這個模塊我們已經在Numpy-C-API例子中遇到過。
* 因為類型映射只支持ARRAY、SIZE的簽名,我們需要將cos_doubles封裝為cos_doubles_func,接收兩個數組包括大小作為輸入。
* 與SWIG不同的是, 我們并沒有包含`cos_doubles.h`頭部,我們并不需要暴露給Python,因為,我們通過`cos_doubles_func`暴露了相關的功能。
并且,和之前一樣,我們可以用`distutils`來封裝這個函數:
In?[?]:
```
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`來制定位置。
In?[?]:
```
$ 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
```
并且,和前面一樣,我們來驗證一下它工作正常:
In?[?]:
```
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()
```

## 2.8.5 Cython
Cython既是寫C擴展的類Python語言,也是這種語言的編譯器。Cython語言是Python的超集,帶有額外的結構,允許你調用C函數和C類型的注釋變量和類屬性。在這個意義上,可以稱之為帶有類型的Python。
除了封裝原生代碼的基礎應用案例,Cython也支持額外的應用案例,即交互優化。從根本上來說,從純Python腳本開始,向瓶頸代碼逐漸增加Cython類型來優化那些確實有影響的代碼。
在這種情況下,與SWIG很相似,因為代碼可以自動生成,但是,從另一個角度來說,又與ctypes很類似,因為,封裝代碼(大部分)是用Python寫的。
盡管其他自動生成代碼的解決方案很難debug(比如SWIG),Cython有一個GNU debugger擴展來幫助debug Python,Cython和C代碼。
**注意** 自動生成的C代碼使用Python-C-Api。
**優點**
* 類Python語言來寫擴展
* 自動生成代碼
* 支持增量優化
* 包含一個GNU debugger擴展
* 支持C++ (從版本0.13) **不足**
* 必須編譯
* 需要額外的庫 ( 只是在build的時候, 在這個問題中,可以通過運送生成的C文件來克服)
### 2.8.5.1 例子
`cos_module`的主要Cython代碼包含在文件`cos_module.pyx`中:
In?[?]:
```
""" 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`的更多代碼:
In?[?]:
```
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"])]
)
```
編譯這個模塊:
In?[?]:
```
$ 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?[?]:
```
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?[?]:
```
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 math庫的聲明,上面的代碼可以簡化為:
In?[?]:
```
""" Simpler example of wrapping cos function from math.h using Cython. """
from libc.math cimport cos
def cos_func(arg):
return cos(arg)
```
在這種情況下,`cimport`語句用于導入`cos`函數。
### 2.8.5.2 Numpy支持
Cython通過`numpy.pyx`文件支持Numpy,允許你為你的Cython代碼添加Numpy數組類型,即就像指定變量`i`是`int`類型,你也可以指定變量`a`是帶有給定的`dtype`的`numpy.ndarray`。同時,同時特定的優化比如邊際檢查也是支持的。看一下[Cython文檔](http://docs.cython.org/src/tutorial/numpy.html)的對應部分。如果你想要將Numpy數組作為C數組傳遞給Cython封裝的C函數,在[Cython wiki](http://wiki.cython.org/tutorials/NumpyPointerToC)上有對應的部分。
在下面的例子中,我們將演示如何用Cython來封裝類似的`cos_doubles`。
In?[?]:
```
void cos_doubles(double * in_array, double * out_array, int size);
```
In?[?]:
```
#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`:
In?[?]:
```
""" Example of wrapping a C function that takes C double arrays as input using
the Numpy declarations from Cython """
# cimport the Cython declarations for numpy
cimport numpy as np
# if you want to use the Numpy-C-API from Cython
# (not strictly necessary for this example, but good practice)
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`來編譯:
In?[?]:
```
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()])],
)
```
與前面的編譯Numpy例子類似,我們需要`include_dirs`選項。
In?[?]:
```
$ 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
```
和前面一樣,我們來驗證一下它是有效的:
In?[?]:
```
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()
```

### 2.8.6 總結
這個部分演示了四種與原生代碼交互的技術。下表概述了這些技術的一些方面。
| 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代碼添加類型來增量優化代碼的技術是惟一的。
## 2.8.7 Further Reading and References
[Ga?l Varoquaux關于避免數據復制的博客](http://gael-varoquaux.info/blog/?p=157)給出了一些如何精明的處理內存管理的見解。如果你在大數據量時出現問題,可以回到這里尋找一些靈感。
## 2.8.8 練習
因為這是一個新部分,練習更像是一個接下來應該查看什么的指示器,因此,看一下那些你覺得更有趣的部分。如果你有關于練習更好點子,請告訴我們!
* 下載每個例子的源碼,并且在你的機器上運行并編譯他們。
* 對每個例子做一些修改,并且自己驗證一下是否有效。 ( 比如,將cos改為sin。)
* 絕大多數例子,特別是包含了Numpy的例子,可能還是比較脆弱,對輸入錯誤反應較差。找一些方法來讓例子崩潰,找出問題所在,并且設計潛在的解決方案。這些是有些點子:
* 數字溢出
* 輸入輸出數組長度不一致
* 多維度數據
* 空數組
* non-double類型數組
* 使用`%timeit`IPython魔法函數來測量不同解決方案的執行時間
### 2.8.8.1 Python-C-API
* 修改Numpy例子以便函數有兩個輸入參數,第二個參數是預分配輸出數組,讓它與其他的Numpy例子一致。
* 修改這個例子,以便這個函數只有一個輸入數組,在原地修改這個函數。
* 試著用新的[Numpy迭代協議](http://docs.scipy.org/doc/numpy/reference/c-api.iterator.html)修改例子。如果你剛好獲得了一個可用的解決方案,請將其提交一個請求到github。
* 你可能注意到了,Numpy-C-API例子只是Numpy例子沒有封裝`cos_doubles`但是直接將`cos`函數應用于Numpy數組的元素上。這樣做與其他技術相比有什么優勢。
* 你可以只用Numpy-C-API來封裝`cos_doubles`。你可能需要確保數組有正確的類型,并且是單維度和內存臨近。
### 2.8.8.2 Ctypes
* 修改Numpy例子以便`cos_doubles_func`為你處理預分配,讓它更像Numpy-C-API例子。
### 2.8.8.3\. SWIG
* 看一下SWIG自動生成的代碼,你能理解多少?
* 修改Numpy例子,以便`cos_doubles_func`為你處理預處理,讓它更像Numpy-C-API例子。
* 修改`cos_doubles` C 函數,以便它返回分配的數組。你可以用SWIG typemaps類封裝嗎? 如果不可以,為什么不可以? 對于這種特殊的情況有沒有什么變通方法? (提示: 你知道輸出數組的大小, 因此,可以從返回的`double \*`構建Numpy數組。
### 2.8.8.4 Cython
* 看一下Cython自動生成的代碼。仔細看一下Cython插入的一些評論。你能看到些什么?
* 看一下Cython文檔中[與Numpy工作](http://docs.cython.org/src/tutorial/numpy.html)的部分, 學習一下如何使用Numpy增量優化python腳本。
* 修改Numpy例子,以便`cos_doubles_func`為你處理預處理,讓它更像Numpy-C-API例子。
- 介紹
- 1.1 科學計算工具及流程
- 1.2 Python語言
- 1.3 NumPy:創建和操作數值數據
- 1.4 Matplotlib:繪圖
- 1.5 Scipy:高級科學計算
- 1.6 獲取幫助及尋找文檔
- 2.1 Python高級功能(Constructs)
- 2.2 高級Numpy
- 2.3 代碼除錯
- 2.4 代碼優化
- 2.5 SciPy中稀疏矩陣
- 2.6 使用Numpy和Scipy進行圖像操作及處理
- 2.7 數學優化:找到函數的最優解
- 2.8 與C進行交互
- 3.1 Python中的統計學
- 3.2 Sympy:Python中的符號數學
- 3.3 Scikit-image:圖像處理
- 3.4 Traits:創建交互對話
- 3.5 使用Mayavi進行3D作圖
- 3.6 scikit-learn:Python中的機器學習