2.8.3. Ctypes
Ctypes是Python的一个外来函数库。它提供了C兼容的数据类型,并且允许在DLLs或者共享的库中调用函数。它可以用来在纯Python中封装这些库。
优点
- Python标准库的一部分
- 不需要编译
- 代码封装都是在Python中
不足
- 需要代码作为一个共享的库(粗略地说,在windows中是 .dll,在Linux中是.so,在Mac OSX中是 *.dylib)
- 对C++支持并不好
2.8.3.1 例子
如前面提到的,代码封装完全在Python中。
In [ ]:
""" 用ctypes封装来自math.h的 cos 函数。 """
import ctypes
from ctypes.util import find_library
# find and load the library
libm = ctypes.cdll.LoadLibrary(find_library('m'))
# set the argument type
libm.cos.argtypes = [ctypes.c_double]
# set the return type
libm.cos.restype = ctypes.c_double
def cos_func(arg):
''' 封装math.h cos函数 '''
return libm.cos(arg)
- 寻找和加载库可能非常依赖于你的操作系统,检查文档来了解细节
- 这可能有些欺骗性,因为math库在系统中已经是编译模式。如果你想要封装一个内置的库,需要先编译它,可能需要或者不需要额外的工作。 我们现在可以像前面一样使用这个库:
In [ ]:
In [1]: import cos_module
In [2]: cos_module?
Type: module
String Form:<module 'cos_module' from 'cos_module.py'>
File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/ctypes/cos_module.py
Docstring: <no docstring>
In [3]: dir(cos_module)
Out[3]:
['__builtins__',
'__doc__',
'__file__',
'__name__',
'__package__',
'cos_func',
'ctypes',
'find_library',
'libm']
In [4]: cos_module.cos_func(1.0)
Out[4]: 0.5403023058681398
In [5]: cos_module.cos_func(0.0)
Out[5]: 1.0
In [6]: cos_module.cos_func(3.14159265359)
Out[6]: -1.0
2.8.3.2 Numpy支持
Numpy包含一些与ctypes交互的支持。特别是支持将特定Numpy数组属性作为ctypes数据类型研究,并且有函数可以将C数组和Numpy数据互相转换。
更多信息,可以看一下Numpy手册的对应部分或者numpy.ndarray.ctypes
和numpy.ctypeslib
的API文档。
对于下面的例子,让我们假设一个C函数,输入输出都是一个数组,计算输入数组的cosine并将结果输出为一个数组。
库包含下列头文件(尽管在这个例子中并不是必须这样,为了完整,我们还是把这一步列出来):
In [ ]:
void cos_doubles(double * in_array, double * out_array, int size);
这个函数实现在下列的C源文件中:
In [ ]:
#include <math.h>
/* Compute the cosine of each element in in_array, storing the result in
* out_array. */
void cos_doubles(double * in_array, double * out_array, int size){
int i;
for(i=0;i<size;i++){
out_array[i] = cos(in_array[i]);
}
}
并且因为这个库是纯C的,我们不能使用distutils
来编译,但是,必须使用make
和gcc
的组合:
In [ ]:
m.PHONY : clean
libcos_doubles.so : cos_doubles.o
gcc -shared -Wl,-soname,libcos_doubles.so -o libcos_doubles.so cos_doubles.o
cos_doubles.o : cos_doubles.c
gcc -c -fPIC cos_doubles.c -o cos_doubles.o
clean :
-rm -vf libcos_doubles.so cos_doubles.o cos_doubles.pyc
接下来,我们可以将这个库编译到共享的库 (on Linux)libcos_doubles.so
:
In [ ]:
$ ls
cos_doubles.c cos_doubles.h cos_doubles.py makefile test_cos_doubles.py
$ make
gcc -c -fPIC cos_doubles.c -o cos_doubles.o
gcc -shared -Wl,-soname,libcos_doubles.so -o libcos_doubles.so cos_doubles.o
$ ls
cos_doubles.c cos_doubles.o libcos_doubles.so* test_cos_doubles.py
cos_doubles.h cos_doubles.py makefile
现在我们可以继续通过ctypes对Numpy数组的直接支持(一定程度上)来封装这个库:
In [ ]:
""" 封装一个使用numpy.ctypeslib接受C双数组作为输入的例子。"""
import numpy as np
import numpy.ctypeslib as npct
from ctypes import c_int
# cos_doubles的输入类型
# 必须是双数组, 有相邻的单维度
array_1d_double = npct.ndpointer(dtype=np.double, ndim=1, flags='CONTIGUOUS')
# 加载库,运用numpy机制
libcd = npct.load_library("libcos_doubles", ".")
# 设置反馈类型和参数类型
libcd.cos_doubles.restype = None
libcd.cos_doubles.argtypes = [array_1d_double, array_1d_double, c_int]
def cos_doubles_func(in_array, out_array):
return libcd.cos_doubles(in_array, out_array, len(in_array))
- 注意临近单维度Numpy数组的固有限制,因为C函数需要这类的缓存器。
- 也需要注意输出数组也需要是预分配的,例如numpy.zeros()和函数将用它的缓存器来写。
- 尽管
cos_doubles
函数的原始签名是ARRAY
,ARRAY
,int
最终的cos_doubles_func
需要两个Numpy数组作为参数。
并且,和前面一样,我们我要为自己证明一下它是有效的:
In [ ]:
import numpy as np
import pylab
import cos_doubles
x = np.arange(0, 2 * np.pi, 0.1)
y = np.empty_like(x)
cos_doubles.cos_doubles_func(x, y)
pylab.plot(x, y)
pylab.show()