# Python 扩展模块 ## 简介 | C Library | Interface | Python | | --- | --- | --- | | `c header` `c implementation` | Wrapper `C` $\leftrightarrows$ `Python` communication between `py + c` | `import fact` `fact.fact(10)` | **Python** 扩展模块将 `PyInt(10)` 转化为 `CInt(10)` 然后调用 `C` 程序中的 `fact()` 函数进行计算,再将返回的结果转换回 `PyInt`。 ## 产生一个扩展模块 假设我们有这样的一个头文件和程序: In [1]: ``` %%file fact.h #ifndef FACT_H #define FACT_h int fact(int n); #endif ``` ``` Writing fact.h ``` In [2]: ``` %%file fact.c #include "fact.h" int fact(int n) { if (n <= 1) return 1; else return n * fact(n - 1); } ``` ``` Writing fact.c ``` 定义包装函数: In [3]: ``` %%file fact_wrap.c /* Must include Python.h before any standard headers*/ #include #include "fact.h" static PyObject* wrap_fact(PyObject *self, PyObject *args) { /* Python->C data conversion */ int n, result; // the string i here means there is only one integer if (!PyArg_ParseTuple(args, "i", &n)) return NULL; /* C Function Call */ result = fact(n); /* C->Python data conversion */ return Py_BuildValue("i", result); } /* Method table declaring the names of functions exposed to Python*/ static PyMethodDef ExampleMethods[] = { {"fact", wrap_fact, METH_VARARGS, "Calculate the factorial of n"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; /* Module initialization function called at "import example"*/ PyMODINIT_FUNC initexample(void) { (void) Py_InitModule("example", ExampleMethods); } ``` ``` Writing fact_wrap.c ``` ## 手动编译扩展模块 手动使用 `gcc` 编译,`Windows` 下如果没有 `gcc`,可以通过 `conda` 进行安装: ``` conda install mingw4 ``` `Window 64-bit` 下编译需要加上 `-DMS_WIN64` 的选项,`include` 和 `lib` 文件夹的路径对应于本地 **Python** 安装的环境: In [4]: ``` !gcc -DMS_WIN64 -c fact.c fact_wrap.c -IC:\Miniconda\include ``` In [5]: ``` !gcc -DMS_WIN64 -shared fact.o fact_wrap.o -LC:\Miniconda\libs -lpython27 -o example.pyd ``` `Windows` 下最终生成的文件后缀为 `.pyd` , `Unix` 下生成的文件后缀名为 `.so`。 用法为: * `Windows 32-bit` ``` gcc -c fact.c fact_wrap.c -I\include gcc -shared fact.o fact_wrap.o -L\libs -lpython27 -o example.pyd ``` * `Unix` ``` gcc -c fact.c fact_wrap.c -I gcc -shared fact.o fact_wrap.o -L\config -lpython27 -o example.so ``` 编译完成后,我们就可以使用 `example` 这个模块了。 导入生成的包: In [6]: ``` import example print dir(example) ``` ``` ['__doc__', '__file__', '__name__', '__package__', 'fact'] ``` 使用 `example` 中的函数: In [7]: ``` print 'factorial of 10:', example.fact(10) ``` ``` factorial of 10: 3628800 ``` ## 使用 setup.py 进行编译 清理刚才生成的文件: In [8]: ``` !rm -f example.pyd ``` 写入 `setup.py`: In [9]: ``` %%file setup.py from distutils.core import setup, Extension ext = Extension(name='example', sources=['fact_wrap.c', 'fact.c']) setup(name='example', ext_modules=[ext]) ``` ``` Writing setup.py ``` 使用 `distutils` 中的函数,我们进行 `build` 和 `install`: ``` python setup.py build (--compiler=mingw64) python setup.py install ``` 括号中的内容在 `windows` 中可能需要加上。 这里我们使用 `build_ext --inplace` 选项将其安装在本地文件夹: In [10]: ``` !python setup.py build_ext --inplace ``` ``` running build_ext building 'example' extension creating build creating build\temp.win-amd64-2.7 creating build\temp.win-amd64-2.7\Release C:\Miniconda\Scripts\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\Miniconda\include -IC:\Miniconda\PC -c fact_wrap.c -o build\temp.win-amd64-2.7\Release\fact_wrap.o C:\Miniconda\Scripts\gcc.bat -DMS_WIN64 -mdll -O -Wall -IC:\Miniconda\include -IC:\Miniconda\PC -c fact.c -o build\temp.win-amd64-2.7\Release\fact.o writing build\temp.win-amd64-2.7\Release\example.def C:\Miniconda\Scripts\gcc.bat -DMS_WIN64 -shared -s build\temp.win-amd64-2.7\Release\fact_wrap.o build\temp.win-amd64-2.7\Release\fact.o build\temp.win-amd64-2.7\Release\example.def -LC:\Miniconda\libs -LC:\Miniconda\PCbuild\amd64 -lpython27 -lmsvcr90 -o "C:\Users\Jin\Documents\Git\python-tutorial\07\. interfacing with other languages\example.pyd" ``` ## 使用编译的模块 进行测试: In [11]: ``` import example print 'factorial of 10:', example.fact(10) ``` ``` factorial of 10: 3628800 ``` 定义 `Python` 函数: In [12]: ``` def pyfact(n): if n <= 1: return 1 return n * pyfact(n-1) print pyfact(10) print example.fact(10) ``` ``` 3628800 3628800 ``` 时间测试: In [13]: ``` %timeit example.fact(10) ``` ``` The slowest run took 13.17 times longer than the fastest. This could mean that an intermediate result is being cached 1000000 loops, best of 3: 213 ns per loop ``` In [14]: ``` %timeit pyfact(10) ``` ``` 1000000 loops, best of 3: 1.43 µs per loop ``` 如果使用 `fact` 计算比较大的值: In [15]: ``` example.fact(100) ``` Out[15]: ``` 0 ``` 会出现溢出的结果,因为 `int` 表示的值有限,但是 `pyfact` 不会有这样的问题: In [16]: ``` pyfact(100) ``` Out[16]: ``` 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L ``` 将生成的文件压缩到压缩文件中: In [17]: ``` import zipfile f = zipfile.ZipFile('07-02-example.zip','w',zipfile.ZIP_DEFLATED) names = 'fact.o fact_wrap.c fact_wrap.o example.pyd setup.py'.split() for name in names: f.write(name) f.close() ``` 清理生成的文件: In [18]: ``` !rm -f fact*.* !rm -f example.* !rm -f setup*.* !rm -rf build ```