这是一个非常流行的设计模式,将函数实现的部分工作委托给用户提供的自定义回调。
在 C 标准库中,最著名的接受这种回调的函数是 qsort()函数,它提供了 Quicksort 算法
的通用实现。你不太可能会直接使用这种算法,而是使用更适合 Python 集合排序的默认
Python Timsort 排序方法。无论如何,qsort()似乎是一个有效的排序算法的典型示例,并
且使用许多编程书把它作为在 C API 中使用回调机制 I 的规范示例。这就是为什么我们将
尝试使用它作为传递 Python 函数作为 C 回调的例子。
普通的 Python 函数类型与 qsort()规范所需的回调函数类型不兼容。这里是来自 BSD
手册页的 qsort()的签名,它还包含接受的回调类型(compare 参数)的类型,如下所示:
void qsort(void *base, size_t nel, size_t width,
int (*compar)(const void , const void ));
所以为了从 libc 执行 qsort(),你需要传递。
● base:这是需要排序的数组,它是 void指针类型。
● nel:这是元素个数,类型为 size_t。
● width:这是数组中单个元素的大小,类型为 size_t。
● comparator:这是指向返回值为 int 类型并接受两个 void指针类型的函数的指
针。它指向比较需要排序的两个元素的大小的函数。
在使用 ctypes 调用 C 函数这部分,我们已经知道如何使用乘法运算符从其他 ctypes
类型构造 C 数组。nel 的类型是 size_t,它映射到 Python 的 int 类型,所以它不需要
任何额外的包装,可以作为 len(迭代器)传递。一旦我们知道 base 数组的类型,width
的值就可以使用 ctypes.sizeof()函数获得。我们需要知道的最后一件事是如何创建一
个指针,该指针指向与 comparator 参数兼容的 Python 函数。
ctypes 模块包含一个 CFUNCTYPE()工厂函数,它允许我们包装 Python 函数,并将
它们表示为 C 可调用的函数指针。第一个参数是包装函数应该返回的 C 返回类型。它后面
是作为函数参数的 C 类型的变量列表。与 qsort()的 comparator 参数兼容的函数类型
是这个样子,如下所示:
CMPFUNC = ctypes.CFUNCTYPE(
返回类型
ctypes.c_int,
第一个参数的类型
ctypes.POINTER(ctypes.c_int),
第二个参数的类型
ctypes.POINTER(ctypes.c_int),
)
把所有事情总结一下,假设我们要从标准 C 库中用一个 qsort()函数对一个随机乱序
的整数数列进行排序。以下是示例脚本,展示如何使用我们至今为止所学习的 ctypes:
from random import shuffle
import ctypes
from ctypes.util import find_library
libc = ctypes.cdll.LoadLibrary(find_library(‘c’))
CMPFUNC = ctypes.CFUNCTYPE(
返回类型
ctypes.c_int,
第一个参数的类型
ctypes.POINTER(ctypes.c_int),
第二个参数的类型
ctypes.POINTER(ctypes.c_int),
)
def ctypes_int_compare(a, b):
参数是指针类型,所以我们可以通过[0]索引访问
print(" %s cmp %s" % (a[0], b[0]))
根据快速排序的规范,应该这样返回:
* 如果 a 小于 b,则小于 0
* 如果 a 等于 b,则等于 0
* 如果 a 大于 b,则大于 0
return a[0] - b[0]
def main():
numbers = list(range(5))
shuffle(numbers)
print("shuffled: ", numbers)
创建一个代表数组的新类型
它和 numbers 列表有相同的长度
NumbersArray = ctypes.c_int * len(numbers)
使用新类型创建一个新的 C 数组
c_array = NumbersArray(*numbers)
libc.qsort(
指向被排序的数组的指针
c_array,
数组长度
len(c_array),
数组中单个元素的大小
ctypes.sizeof(ctypes.c_int),
回调(指向 C 比较函数的指针)
CMPFUNC(ctypes_int_compare)
)
print("sorted: ", list(c_array))
if name == “main”:
main()
作为回调的比较函数有一个额外的 print 语句,因此我们可以看到它在排序过程中是
如何执行的,如下所示:
$ python ctypes_qsort.py
shuffled: [4, 3, 0, 1, 2]
4 cmp 3
4 cmp 0
3 cmp 0
4 cmp 1
3 cmp 1
0 cmp 1
4 cmp 2
3 cmp 2
1 cmp 2
sorted: [0, 1, 2, 3, 4]