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 <Python.h>
- #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<your python path>\include
gcc -shared fact.o fact_wrap.o -L<your python path>\libs -lpython27 -o example.pyd
Unix
gcc -c fact.c fact_wrap.c -I<your python path>
gcc -shared fact.o fact_wrap.o -L<your python path>\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