mirror of
https://github.com/apachecn/ailearning.git
synced 2026-02-03 10:24:39 +08:00
353 lines
5.9 KiB
Markdown
353 lines
5.9 KiB
Markdown
# 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]:
|
||
|
||
```py
|
||
%%file fact.h
|
||
#ifndef FACT_H
|
||
#define FACT_h
|
||
int fact(int n);
|
||
#endif
|
||
|
||
```
|
||
|
||
```py
|
||
Writing fact.h
|
||
|
||
```
|
||
|
||
In [2]:
|
||
|
||
```py
|
||
%%file fact.c
|
||
#include "fact.h"
|
||
int fact(int n)
|
||
{
|
||
if (n <= 1) return 1;
|
||
else return n * fact(n - 1);
|
||
}
|
||
|
||
```
|
||
|
||
```py
|
||
Writing fact.c
|
||
|
||
```
|
||
|
||
定义包装函数:
|
||
|
||
In [3]:
|
||
|
||
```py
|
||
%%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);
|
||
}
|
||
|
||
```
|
||
|
||
```py
|
||
Writing fact_wrap.c
|
||
|
||
```
|
||
|
||
## 手动编译扩展模块
|
||
|
||
手动使用 `gcc` 编译,`Windows` 下如果没有 `gcc`,可以通过 `conda` 进行安装:
|
||
|
||
```py
|
||
conda install mingw4
|
||
```
|
||
|
||
`Window 64-bit` 下编译需要加上 `-DMS_WIN64` 的选项,`include` 和 `lib` 文件夹的路径对应于本地 **Python** 安装的环境:
|
||
|
||
In [4]:
|
||
|
||
```py
|
||
!gcc -DMS_WIN64 -c fact.c fact_wrap.c -IC:\Miniconda\include
|
||
|
||
```
|
||
|
||
In [5]:
|
||
|
||
```py
|
||
!gcc -DMS_WIN64 -shared fact.o fact_wrap.o -LC:\Miniconda\libs -lpython27 -o example.pyd
|
||
|
||
```
|
||
|
||
`Windows` 下最终生成的文件后缀为 `.pyd` , `Unix` 下生成的文件后缀名为 `.so`。
|
||
|
||
用法为:
|
||
|
||
* `Windows 32-bit`
|
||
|
||
```py
|
||
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`
|
||
|
||
```py
|
||
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]:
|
||
|
||
```py
|
||
import example
|
||
print dir(example)
|
||
|
||
```
|
||
|
||
```py
|
||
['__doc__', '__file__', '__name__', '__package__', 'fact']
|
||
|
||
```
|
||
|
||
使用 `example` 中的函数:
|
||
|
||
In [7]:
|
||
|
||
```py
|
||
print 'factorial of 10:', example.fact(10)
|
||
|
||
```
|
||
|
||
```py
|
||
factorial of 10: 3628800
|
||
|
||
```
|
||
|
||
## 使用 setup.py 进行编译
|
||
|
||
清理刚才生成的文件:
|
||
|
||
In [8]:
|
||
|
||
```py
|
||
!rm -f example.pyd
|
||
|
||
```
|
||
|
||
写入 `setup.py`:
|
||
|
||
In [9]:
|
||
|
||
```py
|
||
%%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])
|
||
|
||
```
|
||
|
||
```py
|
||
Writing setup.py
|
||
|
||
```
|
||
|
||
使用 `distutils` 中的函数,我们进行 `build` 和 `install`:
|
||
|
||
```py
|
||
python setup.py build (--compiler=mingw64)
|
||
python setup.py install
|
||
```
|
||
|
||
括号中的内容在 `windows` 中可能需要加上。
|
||
|
||
这里我们使用 `build_ext --inplace` 选项将其安装在本地文件夹:
|
||
|
||
In [10]:
|
||
|
||
```py
|
||
!python setup.py build_ext --inplace
|
||
|
||
```
|
||
|
||
```py
|
||
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]:
|
||
|
||
```py
|
||
import example
|
||
|
||
print 'factorial of 10:', example.fact(10)
|
||
|
||
```
|
||
|
||
```py
|
||
factorial of 10: 3628800
|
||
|
||
```
|
||
|
||
定义 `Python` 函数:
|
||
|
||
In [12]:
|
||
|
||
```py
|
||
def pyfact(n):
|
||
if n <= 1: return 1
|
||
return n * pyfact(n-1)
|
||
|
||
print pyfact(10)
|
||
print example.fact(10)
|
||
|
||
```
|
||
|
||
```py
|
||
3628800
|
||
3628800
|
||
|
||
```
|
||
|
||
时间测试:
|
||
|
||
In [13]:
|
||
|
||
```py
|
||
%timeit example.fact(10)
|
||
|
||
```
|
||
|
||
```py
|
||
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]:
|
||
|
||
```py
|
||
%timeit pyfact(10)
|
||
|
||
```
|
||
|
||
```py
|
||
1000000 loops, best of 3: 1.43 µs per loop
|
||
|
||
```
|
||
|
||
如果使用 `fact` 计算比较大的值:
|
||
|
||
In [15]:
|
||
|
||
```py
|
||
example.fact(100)
|
||
|
||
```
|
||
|
||
Out[15]:
|
||
|
||
```py
|
||
0
|
||
```
|
||
|
||
会出现溢出的结果,因为 `int` 表示的值有限,但是 `pyfact` 不会有这样的问题:
|
||
|
||
In [16]:
|
||
|
||
```py
|
||
pyfact(100)
|
||
|
||
```
|
||
|
||
Out[16]:
|
||
|
||
```py
|
||
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
|
||
```
|
||
|
||
将生成的文件压缩到压缩文件中:
|
||
|
||
In [17]:
|
||
|
||
```py
|
||
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]:
|
||
|
||
```py
|
||
!rm -f fact*.*
|
||
!rm -f example.*
|
||
!rm -f setup*.*
|
||
!rm -rf build
|
||
|
||
``` |