#!/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')