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]:

  1. %%file fact.h
  2. #ifndef FACT_H
  3. #define FACT_h
  4. int fact(int n);
  5. #endif
  1. Writing fact.h

In [2]:

  1. %%file fact.c
  2. #include "fact.h"
  3. int fact(int n)
  4. {
  5. if (n <= 1) return 1;
  6. else return n * fact(n - 1);
  7. }
  1. Writing fact.c

定义包装函数:

In [3]:

  1. %%file fact_wrap.c
  2.  
  3. /* Must include Python.h before any standard headers*/
  4. #include <Python.h>
  5. #include "fact.h"
  6. static PyObject* wrap_fact(PyObject *self, PyObject *args)
  7. {
  8. /* Python->C data conversion */
  9. int n, result;
  10. // the string i here means there is only one integer
  11. if (!PyArg_ParseTuple(args, "i", &n))
  12. return NULL;
  13.  
  14. /* C Function Call */
  15. result = fact(n);
  16.  
  17. /* C->Python data conversion */
  18. return Py_BuildValue("i", result);
  19. }
  20.  
  21. /* Method table declaring the names of functions exposed to Python*/
  22. static PyMethodDef ExampleMethods[] = {
  23. {"fact", wrap_fact, METH_VARARGS, "Calculate the factorial of n"},
  24. {NULL, NULL, 0, NULL} /* Sentinel */
  25. };
  26.  
  27. /* Module initialization function called at "import example"*/
  28. PyMODINIT_FUNC
  29. initexample(void)
  30. {
  31. (void) Py_InitModule("example", ExampleMethods);
  32. }
  1. Writing fact_wrap.c

手动编译扩展模块

手动使用 gcc 编译,Windows 下如果没有 gcc,可以通过 conda 进行安装:

  1. conda install mingw4

Window 64-bit 下编译需要加上 -DMS_WIN64 的选项,includelib 文件夹的路径对应于本地 Python 安装的环境:

In [4]:

  1. !gcc -DMS_WIN64 -c fact.c fact_wrap.c -IC:\Miniconda\include

In [5]:

  1. !gcc -DMS_WIN64 -shared fact.o fact_wrap.o -LC:\Miniconda\libs -lpython27 -o example.pyd

Windows 下最终生成的文件后缀为 .pydUnix 下生成的文件后缀名为 .so

用法为:

  • Windows 32-bit
  1. gcc -c fact.c fact_wrap.c -I<your python path>\include
  2. gcc -shared fact.o fact_wrap.o -L<your python path>\libs -lpython27 -o example.pyd
  • Unix
  1. gcc -c fact.c fact_wrap.c -I<your python path>
  2. gcc -shared fact.o fact_wrap.o -L<your python path>\config -lpython27 -o example.so

编译完成后,我们就可以使用 example 这个模块了。

导入生成的包:

In [6]:

  1. import example
  2. print dir(example)
  1. ['__doc__', '__file__', '__name__', '__package__', 'fact']

使用 example 中的函数:

In [7]:

  1. print 'factorial of 10:', example.fact(10)
  1. factorial of 10: 3628800

使用 setup.py 进行编译

清理刚才生成的文件:

In [8]:

  1. !rm -f example.pyd

写入 setup.py

In [9]:

  1. %%file setup.py
  2. from distutils.core import setup, Extension
  3.  
  4. ext = Extension(name='example', sources=['fact_wrap.c', 'fact.c'])
  5.  
  6. setup(name='example', ext_modules=[ext])
  1. Writing setup.py

使用 distutils 中的函数,我们进行 buildinstall

  1. python setup.py build (--compiler=mingw64)
  2. python setup.py install

括号中的内容在 windows 中可能需要加上。

这里我们使用 build_ext —inplace 选项将其安装在本地文件夹:

In [10]:

  1. !python setup.py build_ext --inplace
  1. running build_ext
  2. building 'example' extension
  3. creating build
  4. creating build\temp.win-amd64-2.7
  5. creating build\temp.win-amd64-2.7\Release
  6. 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
  7. 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
  8. writing build\temp.win-amd64-2.7\Release\example.def
  9. 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]:

  1. import example
  2.  
  3. print 'factorial of 10:', example.fact(10)
  1. factorial of 10: 3628800

定义 Python 函数:

In [12]:

  1. def pyfact(n):
  2. if n <= 1: return 1
  3. return n * pyfact(n-1)
  4.  
  5. print pyfact(10)
  6. print example.fact(10)
  1. 3628800
  2. 3628800

时间测试:

In [13]:

  1. %timeit example.fact(10)
  1. The slowest run took 13.17 times longer than the fastest. This could mean that an intermediate result is being cached
  2. 1000000 loops, best of 3: 213 ns per loop

In [14]:

  1. %timeit pyfact(10)
  1. 1000000 loops, best of 3: 1.43 µs per loop

如果使用 fact 计算比较大的值:

In [15]:

  1. example.fact(100)

Out[15]:

  1. 0

会出现溢出的结果,因为 int 表示的值有限,但是 pyfact 不会有这样的问题:

In [16]:

  1. pyfact(100)

Out[16]:

  1. 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L

将生成的文件压缩到压缩文件中:

In [17]:

  1. import zipfile
  2.  
  3. f = zipfile.ZipFile('07-02-example.zip','w',zipfile.ZIP_DEFLATED)
  4.  
  5. names = 'fact.o fact_wrap.c fact_wrap.o example.pyd setup.py'.split()
  6. for name in names:
  7. f.write(name)
  8.  
  9. f.close()

清理生成的文件:

In [18]:

  1. !rm -f fact*.*
  2. !rm -f example.*
  3. !rm -f setup*.*
  4. !rm -rf build

原文: https://nbviewer.jupyter.org/github/lijin-THU/notes-python/blob/master/07-interfacing-with-other-languages/07.02-python-extension-modules.ipynb