#!/usr/bin/env python # coding: utf-8 # # 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]: get_ipython().run_cell_magic('file', 'fact.h', '#ifndef FACT_H\n#define FACT_h\nint fact(int n);\n#endif\n') # In[2]: get_ipython().run_cell_magic('file', 'fact.c', '#include "fact.h"\nint fact(int n)\n{\n if (n <= 1) return 1;\n else return n * fact(n - 1);\n}\n') # 定义包装函数: # In[3]: get_ipython().run_cell_magic('file', 'fact_wrap.c', '\n/* Must include Python.h before any standard headers*/\n#include \n#include "fact.h"\nstatic PyObject* wrap_fact(PyObject *self, PyObject *args)\n{\n /* Python->C data conversion */\n int n, result;\n // the string i here means there is only one integer\n if (!PyArg_ParseTuple(args, "i", &n))\n return NULL;\n \n /* C Function Call */\n result = fact(n);\n \n /* C->Python data conversion */\n return Py_BuildValue("i", result);\n}\n\n/* Method table declaring the names of functions exposed to Python*/\nstatic PyMethodDef ExampleMethods[] = {\n {"fact", wrap_fact, METH_VARARGS, "Calculate the factorial of n"},\n {NULL, NULL, 0, NULL} /* Sentinel */\n};\n\n/* Module initialization function called at "import example"*/\nPyMODINIT_FUNC \ninitexample(void)\n{\n (void) Py_InitModule("example", ExampleMethods);\n}\n') # ## 手动编译扩展模块 # 手动使用 `gcc` 编译,`Windows` 下如果没有 `gcc`,可以通过 `conda` 进行安装: # # conda install mingw4 # # `Window 64-bit` 下编译需要加上 `-DMS_WIN64` 的选项,`include` 和 `lib` 文件夹的路径对应于本地 **Python** 安装的环境: # In[4]: get_ipython().system('gcc -DMS_WIN64 -c fact.c fact_wrap.c -IC:\\Miniconda\\include') # In[5]: get_ipython().system('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) # 使用 `example` 中的函数: # In[7]: print 'factorial of 10:', example.fact(10) # ## 使用 setup.py 进行编译 # 清理刚才生成的文件: # In[8]: get_ipython().system('rm -f example.pyd') # 写入 `setup.py`: # In[9]: get_ipython().run_cell_magic('file', 'setup.py', "from distutils.core import setup, Extension\n\next = Extension(name='example', sources=['fact_wrap.c', 'fact.c'])\n\nsetup(name='example', ext_modules=[ext])\n") # 使用 `distutils` 中的函数,我们进行 `build` 和 `install`: # # python setup.py build (--compiler=mingw64) # python setup.py install # # 括号中的内容在 `windows` 中可能需要加上。 # # 这里我们使用 `build_ext --inplace` 选项将其安装在本地文件夹: # In[10]: get_ipython().system('python setup.py build_ext --inplace') # ## 使用编译的模块 # 进行测试: # In[11]: import example print 'factorial of 10:', example.fact(10) # 定义 `Python` 函数: # In[12]: def pyfact(n): if n <= 1: return 1 return n * pyfact(n-1) print pyfact(10) print example.fact(10) # 时间测试: # In[13]: get_ipython().run_line_magic('timeit', 'example.fact(10)') # In[14]: get_ipython().run_line_magic('timeit', 'pyfact(10)') # 如果使用 `fact` 计算比较大的值: # In[15]: example.fact(100) # 会出现溢出的结果,因为 `int` 表示的值有限,但是 `pyfact` 不会有这样的问题: # In[16]: pyfact(100) # 将生成的文件压缩到压缩文件中: # 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]: get_ipython().system('rm -f fact*.*') get_ipython().system('rm -f example.*') get_ipython().system('rm -f setup*.*') get_ipython().system('rm -rf build')