本文的翻译是专门为Python开发人员课程的学生准备的。
注意:本文中的代码已获得GNU AGPLv3的许可。我写本指南的原因是找不到与ctypes有关的所有有用内容的指南。我希望这篇文章可以使某人的生活变得更轻松。内容:- 基本优化
- 样式
- Python编译
- Python中的结构
- 用C调用您的代码
- y
基本优化
在用C重写Python源代码之前,请考虑Python中的基本优化方法。内置数据结构
Python的内置数据结构(例如set和dict)是用C编写的。它们的工作速度比您自己编写为Python类的数据结构要快得多。集合模块文档中介绍了除标准集,字典,列表和元组以外的其他数据结构。列出表达式
不要使用标准方法将项目添加到列表中,而要使用列表表达式。
mapped = []
for value in originallist:
mapped.append(myfunc(value))
mapped = [myfunc(value) in originallist]
类型
ctypes
模块允许您与Python中的C代码进行交互,而无需使用模块subprocess
或其他类似模块来从CLI启动其他进程。只有两部分:编译C代码以提高质量,shared object
并在Python代码中设置数据结构以将其映射到类型C。在本文中,我将把我的Python代码与lcs.c连接,该代码将找到两个子序列中最长的子序列。字符串列表。我希望以下内容可以在Python中工作:list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
common = lcs(list1, list2)
print(common)
一个问题是,这个特定的C函数是一个函数的签名,该函数将字符串列表作为参数类型,并返回一个没有固定长度的类型。我用包含指针和长度的序列结构解决了这个问题。用Python编译C代码
首先,将C源代码(lcs.c)编译为lcs.so
要在Python 中加载。gcc -c -Wall -Werror -fpic -O3 lcs.c -o lcs.o
gcc -shared -o lcs.so lcs.o
- - 沃尔将显示所有警告;
- - Werror将包裹在错误的所有警告;
- - 如果要在Python中使用此库,fpic将生成与位置无关的指令;
- - O3最大化优化;
现在,我们将开始使用生成的共享库文件来编写Python代码。Python中的结构
下面是我的C代码中使用的两个数据结构。struct Sequence
{
char **items;
int length;
};
struct Cell
{
int index;
int length;
struct Cell *prev;
};
这些是这些结构到Python的翻译。import ctypes
class SEQUENCE(ctypes.Structure):
_fields_ = [('items', ctypes.POINTER(ctypes.c_char_p)),
('length', ctypes.c_int)]
class CELL(ctypes.Structure):
pass
CELL._fields_ = [('index', ctypes.c_int), ('length', ctypes.c_int),
('prev', ctypes.POINTER(CELL))]
一些注意事项:- 所有结构都是从继承的类
ctypes.Structure
。 - 唯一的字段
_fields_
是元组列表。每个元组是(<variable-name>
,<ctypes.TYPE>
)。 - c_char(char)和c_char_p(* char)中有
ctypes
相似的类型。 - 还有
ctypes
一种POINTER()
可以根据传递给它的每种类型来创建类型指针。 - 如果您有类似in的递归定义
CELL
,则必须传递初始声明,然后添加字段_fields_
,以便以后获得指向您自己的链接。 - 由于我没有
CELL
在代码中使用Python,因此不需要编写此结构,但是它在递归字段中具有有趣的属性。
用C调用您的代码
另外,我需要一些代码将Python类型转换为C中的新结构。现在,您可以使用新的C函数来加快Python代码的速度。def list_to_SEQUENCE(strlist: List[str]) -> SEQUENCE:
bytelist = [bytes(s, 'utf-8') for s in strlist]
arr = (ctypes.c_char_p * len(bytelist))()
arr[:] = bytelist
return SEQUENCE(arr, len(bytelist))
def lcs(s1: List[str], s2: List[str]) -> List[str]:
seq1 = list_to_SEQUENCE(s1)
seq2 = list_to_SEQUENCE(s2)
common = lcsmodule.lcs(ctypes.byref(seq1), ctypes.byref(seq2))[0]
ret = []
for i in range(common.length):
ret.append(common.items[i].decode('utf-8'))
lcsmodule.freeSequence(common)
return ret
lcsmodule = ctypes.cdll.LoadLibrary('lcsmodule/lcs.so')
lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE)
list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
common = lcs(list1, list2)
print(common)
一些注意事项:**char
(字符串列表)直接匹配Python中的字节列表。- 有
lcs.c
一个lcs()
带有签名struct Sequence * lcs(struct Sequence * s1,struct Sequence * s2)的函数。要设置返回类型,我使用lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE)
。 - 要使用对Sequence结构的引用进行调用,我使用了
ctypes.byref()
一种将“轻型指针”返回到您的对象的方法(比快ctypes.POINTER()
)。 common.items
-这是一个字节列表,可以将它们解码为ret
list形式str
。- lcsmodule.freeSequence(公共)仅释放与公共关联的内存。这很重要,因为垃圾收集器(AFAIK)不会自动收集它。
优化的Python代码是您用C编写并包装在Python中的代码。更多:PyPy
注意:我本人从未使用过PyPy。最简单的优化方法之一是在PyPy运行时中运行程序,该运行时包含JIT编译器(及时),可加快循环的工作,将其编译为机器代码以重复执行。如果您有意见或要讨论什么,请写信给我(samuel.robert.stevens@gmail.com)。就这样。在课程中见!