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

  1. """ 用ctypes封装来自math.h的 cos 函数。 """
  2. import ctypes
  3. from ctypes.util import find_library
  4. # find and load the library
  5. libm = ctypes.cdll.LoadLibrary(find_library('m'))
  6. # set the argument type
  7. libm.cos.argtypes = [ctypes.c_double]
  8. # set the return type
  9. libm.cos.restype = ctypes.c_double
  10. def cos_func(arg):
  11. ''' 封装math.h cos函数 '''
  12. return libm.cos(arg)
  • 寻找和加载库可能非常依赖于你的操作系统,检查文档来了解细节
  • 这可能有些欺骗性,因为math库在系统中已经是编译模式。如果你想要封装一个内置的库,需要先编译它,可能需要或者不需要额外的工作。 我们现在可以像前面一样使用这个库:

In [ ]:

  1. In [1]: import cos_module
  2. In [2]: cos_module?
  3. Type: module
  4. String Form:<module 'cos_module' from 'cos_module.py'>
  5. File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/ctypes/cos_module.py
  6. Docstring: <no docstring>
  7. In [3]: dir(cos_module)
  8. Out[3]:
  9. ['__builtins__',
  10. '__doc__',
  11. '__file__',
  12. '__name__',
  13. '__package__',
  14. 'cos_func',
  15. 'ctypes',
  16. 'find_library',
  17. 'libm']
  18. In [4]: cos_module.cos_func(1.0)
  19. Out[4]: 0.5403023058681398
  20. In [5]: cos_module.cos_func(0.0)
  21. Out[5]: 1.0
  22. In [6]: cos_module.cos_func(3.14159265359)
  23. Out[6]: -1.0

2.8.3.2 Numpy支持

Numpy包含一些与ctypes交互的支持。特别是支持将特定Numpy数组属性作为ctypes数据类型研究,并且有函数可以将C数组和Numpy数据互相转换。

更多信息,可以看一下Numpy手册的对应部分或者numpy.ndarray.ctypesnumpy.ctypeslib的API文档。

对于下面的例子,让我们假设一个C函数,输入输出都是一个数组,计算输入数组的cosine并将结果输出为一个数组。

库包含下列头文件(尽管在这个例子中并不是必须这样,为了完整,我们还是把这一步列出来):

In [ ]:

  1. void cos_doubles(double * in_array, double * out_array, int size);

这个函数实现在下列的C源文件中:

In [ ]:

  1. #include <math.h>
  2. /* Compute the cosine of each element in in_array, storing the result in
  3. * out_array. */
  4. void cos_doubles(double * in_array, double * out_array, int size){
  5. int i;
  6. for(i=0;i<size;i++){
  7. out_array[i] = cos(in_array[i]);
  8. }
  9. }

并且因为这个库是纯C的,我们不能使用distutils来编译,但是,必须使用makegcc的组合:

In [ ]:

  1. m.PHONY : clean
  2. libcos_doubles.so : cos_doubles.o
  3. gcc -shared -Wl,-soname,libcos_doubles.so -o libcos_doubles.so cos_doubles.o
  4. cos_doubles.o : cos_doubles.c
  5. gcc -c -fPIC cos_doubles.c -o cos_doubles.o
  6. clean :
  7. -rm -vf libcos_doubles.so cos_doubles.o cos_doubles.pyc

接下来,我们可以将这个库编译到共享的库 (on Linux)libcos_doubles.so:

In [ ]:

  1. $ ls
  2. cos_doubles.c cos_doubles.h cos_doubles.py makefile test_cos_doubles.py
  3. $ make
  4. gcc -c -fPIC cos_doubles.c -o cos_doubles.o
  5. gcc -shared -Wl,-soname,libcos_doubles.so -o libcos_doubles.so cos_doubles.o
  6. $ ls
  7. cos_doubles.c cos_doubles.o libcos_doubles.so* test_cos_doubles.py
  8. cos_doubles.h cos_doubles.py makefile

现在我们可以继续通过ctypes对Numpy数组的直接支持(一定程度上)来封装这个库:

In [ ]:

  1. """ 封装一个使用numpy.ctypeslib接受C双数组作为输入的例子。"""
  2. import numpy as np
  3. import numpy.ctypeslib as npct
  4. from ctypes import c_int
  5. # cos_doubles的输入类型
  6. # 必须是双数组, 有相邻的单维度
  7. array_1d_double = npct.ndpointer(dtype=np.double, ndim=1, flags='CONTIGUOUS')
  8. # 加载库,运用numpy机制
  9. libcd = npct.load_library("libcos_doubles", ".")
  10. # 设置反馈类型和参数类型
  11. libcd.cos_doubles.restype = None
  12. libcd.cos_doubles.argtypes = [array_1d_double, array_1d_double, c_int]
  13. def cos_doubles_func(in_array, out_array):
  14. 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 [ ]:

  1. import numpy as np
  2. import pylab
  3. import cos_doubles
  4. x = np.arange(0, 2 * np.pi, 0.1)
  5. y = np.empty_like(x)
  6. cos_doubles.cos_doubles_func(x, y)
  7. pylab.plot(x, y)
  8. pylab.show()

2.8.3. Ctypes - 图1