Python从小白到高手实现系列三百二十六:传递Python 函数作为C 回调
这是一个非常流行的设计模式,将函数实现的部分工作委托给用户提供的自定义回调。在C 标准库中,最著名的接受这种回调的函数是qsort()函数,它提供了Quicksort 算法的通用实现。你不太可能会直接使用这种算法,而是使用更适合Python 集合排序的默认Python Timsort 排序方法。无论如何,qsort()似乎是一个有效的排序算法的典型示例,并且使用许多编程书把它作为在C API 中使
这是一个非常流行的设计模式,将函数实现的部分工作委托给用户提供的自定义回调。
在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),
)
CFUNCTYPE()使用cdecl 调用约定,因此它只与CDLL 和
PyDLL共享库兼容。在Windows 上使用WinDLL或OleDLL
加载的动态库使用stdcall 调用约定。这意味着必须使
用其他工厂把Python 函数包装为C 可调用函数指针。在
ctypes 中,WINFUNCTYPE()可以完成这个包装过程。
把所有事情总结一下,假设我们要从标准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]

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。
更多推荐
所有评论(0)