各位老铁,今天咱们来聊聊Python的高级玩法:用C扩展Python,让你的Python代码像打了鸡血一样飞起来!
开场白:Python虽好,但有时也得C来帮忙
Python这门语言,上手容易,库也多,简直是编程界的瑞士军刀。但是,有些时候,Python的性能会成为瓶颈。比如,需要进行大量的数值计算、图像处理,或者要调用一些底层硬件接口的时候,Python就有点力不从心了。这时候,我们就需要请出我们的秘密武器——C语言。
C语言以其运行效率高、控制力强而闻名。通过C扩展,我们可以把一些性能敏感的代码用C语言编写,然后让Python来调用,这样既能享受到Python的便利性,又能保证程序的运行效率。
第一部分:为什么要用C扩展?
在深入技术细节之前,咱们先来聊聊为什么要用C扩展,而不是用其他方法优化Python代码。
-
性能!性能!还是性能!
这是最主要的原因。C语言编译后的代码直接运行在硬件上,效率比Python解释器高得多。对于一些计算密集型的任务,C扩展可以带来数量级的性能提升。
-
访问底层资源
Python是高级语言,对底层硬件的访问能力有限。而C语言可以直接访问内存、寄存器等底层资源,可以实现一些Python无法实现的功能。
-
复用现有的C/C++代码
很多优秀的库是用C/C++编写的。通过C扩展,我们可以直接在Python中使用这些库,而不需要重新编写。
-
保护你的代码
Python是解释型语言,源代码容易被反编译。而C语言编译后的代码是二进制文件,更难被破解。当然,这只是相对的,没有绝对的安全。
第二部分:准备工作
在开始编写C扩展之前,我们需要做一些准备工作。
-
安装Python开发包
在Linux系统上,通常需要安装
python-dev
或python3-dev
包。在Windows系统上,需要安装Visual Studio,并确保Python的include目录和libs目录已经添加到Visual Studio的搜索路径中。例如在Ubuntu系统上,你可以使用以下命令安装:
sudo apt-get update sudo apt-get install python3-dev
-
安装必要的构建工具
我们需要使用
setuptools
或者distutils
来构建我们的C扩展模块。通常情况下,Python已经自带了setuptools
,如果没有,可以使用pip
安装。pip install setuptools
第三部分:编写C扩展
接下来,咱们来编写一个简单的C扩展模块,实现一个加法函数。
-
创建C源文件
创建一个名为
my_module.c
的文件,内容如下:#include <Python.h> // C 函数:两个整数相加 static PyObject* my_module_add(PyObject* self, PyObject* args) { int a, b; // 从 Python 传递的参数中解析出两个整数 if (!PyArg_ParseTuple(args, "ii", &a, &b)) { return NULL; // 参数解析失败,返回 NULL } // 计算结果 int result = a + b; // 将结果转换为 Python 对象并返回 return PyLong_FromLong(result); } // 方法列表:定义模块中可用的函数 static PyMethodDef MyModuleMethods[] = { {"add", my_module_add, METH_VARARGS, "Adds two integers."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; // 模块定义结构体 static struct PyModuleDef mymodule = { PyModuleDef_HEAD_INIT, "my_module", // 模块名 "A sample module", // 模块文档 -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ MyModuleMethods }; // 模块初始化函数 PyMODINIT_FUNC PyInit_my_module(void) { return PyModule_Create(&mymodule); }
代码解释:
#include <Python.h>
: 必须包含的头文件,包含了Python C API的定义。my_module_add
: 这是一个C函数,实现了加法功能。它接受两个参数:self
和args
。self
通常不用管,args
是一个Python元组,包含了从Python传递过来的参数。PyArg_ParseTuple
: 这个函数用于从args
中解析出参数。"ii"
表示期望解析两个整数,&a
和&b
是用于存储解析结果的变量的地址。如果解析失败,返回NULL
。PyLong_FromLong
: 这个函数用于将C语言的整数转换为Python的整数对象。MyModuleMethods
: 这是一个方法列表,定义了模块中可用的函数。每一项包含四个元素:函数名、C函数指针、参数类型、文档字符串。最后一项必须是{NULL, NULL, 0, NULL}
,作为哨兵。METH_VARARGS
表示该函数接受任意个数的位置参数。PyModuleDef
: 模块定义结构体,包含了模块的名称、文档、方法列表等信息。PyInit_my_module
: 模块初始化函数。这个函数在Python导入模块时被调用。它的名称必须是PyInit_
加上模块名。它返回一个指向模块对象的指针。
-
创建setup.py文件
创建一个名为
setup.py
的文件,内容如下:from setuptools import setup, Extension module1 = Extension('my_module', sources = ['my_module.c']) setup (name = 'MyModule', version = '1.0', description = 'This is a demo package', ext_modules = [module1])
代码解释:
from setuptools import setup, Extension
: 导入setup
和Extension
类。Extension
: 创建一个Extension
对象,用于描述C扩展模块。第一个参数是模块名,第二个参数是源文件列表。setup
: 调用setup
函数,用于构建和安装C扩展模块。name
是包名,version
是版本号,description
是描述信息,ext_modules
是C扩展模块列表。
第四部分:编译和安装C扩展
在命令行中,进入包含setup.py
文件的目录,执行以下命令:
python setup.py build_ext --inplace
这个命令会编译C扩展模块,并在当前目录下生成一个my_module.so
(Linux) 或 my_module.pyd
(Windows) 文件。--inplace
选项表示在当前目录下生成文件,而不是在build
目录下。
如果你想安装到Python的site-packages目录下,可以使用以下命令:
python setup.py install
这需要管理员权限。
第五部分:在Python中使用C扩展
现在,我们可以在Python中使用我们编写的C扩展模块了。
import my_module
# 调用C扩展模块中的 add 函数
result = my_module.add(10, 20)
print(result) # 输出: 30
第六部分:更复杂的例子:操作Python列表
咱们再来一个更复杂的例子,演示如何在C扩展中操作Python列表。
-
修改C源文件
修改
my_module.c
文件,添加一个函数,用于计算Python列表中所有元素的和。#include <Python.h> // C 函数:两个整数相加 static PyObject* my_module_add(PyObject* self, PyObject* args) { int a, b; // 从 Python 传递的参数中解析出两个整数 if (!PyArg_ParseTuple(args, "ii", &a, &b)) { return NULL; // 参数解析失败,返回 NULL } // 计算结果 int result = a + b; // 将结果转换为 Python 对象并返回 return PyLong_FromLong(result); } // C 函数:计算 Python 列表中所有元素的和 static PyObject* my_module_sum_list(PyObject* self, PyObject* args) { PyObject *listObj; long sum = 0; int i, listSize; // 从 Python 传递的参数中解析出列表对象 if (!PyArg_ParseTuple(args, "O", &listObj)) { return NULL; // 参数解析失败,返回 NULL } // 检查是否是列表对象 if (!PyList_Check(listObj)) { PyErr_SetString(PyExc_TypeError, "Argument must be a list"); return NULL; } // 获取列表大小 listSize = PyList_Size(listObj); // 遍历列表,计算所有元素的和 for (i = 0; i < listSize; i++) { PyObject *item = PyList_GetItem(listObj, i); // 不增加引用计数 long value = PyLong_AsLong(item); if (PyErr_Occurred()) { return NULL; // 转换失败,返回 NULL } sum += value; } // 将结果转换为 Python 对象并返回 return PyLong_FromLong(sum); } // 方法列表:定义模块中可用的函数 static PyMethodDef MyModuleMethods[] = { {"add", my_module_add, METH_VARARGS, "Adds two integers."}, {"sum_list", my_module_sum_list, METH_VARARGS, "Calculates the sum of a list."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; // 模块定义结构体 static struct PyModuleDef mymodule = { PyModuleDef_HEAD_INIT, "my_module", // 模块名 "A sample module", // 模块文档 -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ MyModuleMethods }; // 模块初始化函数 PyMODINIT_FUNC PyInit_my_module(void) { return PyModule_Create(&mymodule); }
代码解释:
my_module_sum_list
: 这是一个C函数,实现了计算Python列表中所有元素和的功能。PyArg_ParseTuple(args, "O", &listObj)
:"O"
表示期望解析一个Python对象。PyList_Check
: 检查listObj
是否是列表对象。PyList_Size
: 获取列表的大小。PyList_GetItem
: 获取列表中指定索引的元素。注意:这个函数返回的是一个 borrowed reference,也就是不增加引用计数。 如果需要长期持有这个对象,需要调用Py_INCREF
增加引用计数,用完后调用Py_DECREF
减少引用计数,防止内存泄漏。PyLong_AsLong
: 将Python的整数对象转换为C语言的整数。PyErr_Occurred
: 检查是否发生了错误。如果发生了错误,返回非NULL
。PyExc_TypeError
: 一个Python异常类型,表示类型错误。PyErr_SetString
: 设置一个Python异常信息。
-
重新编译和安装C扩展
执行以下命令重新编译和安装C扩展:
python setup.py build_ext --inplace
-
在Python中使用C扩展
import my_module # 调用C扩展模块中的 sum_list 函数 my_list = [1, 2, 3, 4, 5] result = my_module.sum_list(my_list) print(result) # 输出: 15
第七部分:错误处理
在C扩展中,错误处理非常重要。如果C代码发生错误,需要将错误信息传递给Python,让Python能够正确处理。
-
设置异常
可以使用
PyErr_SetString
函数设置异常信息。第一个参数是异常类型,第二个参数是错误信息。例如:PyErr_SetString(PyExc_TypeError, "Invalid argument type"); return NULL;
-
返回NULL
如果C函数发生错误,应该返回
NULL
。Python解释器会检查返回值是否为NULL
,如果是,就会抛出一个异常。 -
清理资源
在C函数返回之前,需要清理所有分配的资源,例如内存、文件句柄等。
第八部分:引用计数
Python使用引用计数来管理内存。每个Python对象都有一个引用计数,表示有多少个指针指向这个对象。当引用计数为0时,对象就会被释放。
在C扩展中,需要特别注意引用计数的管理。如果忘记增加或减少引用计数,可能会导致内存泄漏或程序崩溃。
-
增加引用计数
可以使用
Py_INCREF
函数增加引用计数。例如:Py_INCREF(obj);
-
减少引用计数
可以使用
Py_DECREF
函数减少引用计数。例如:Py_DECREF(obj);
-
Borrowed reference
有些函数返回的是 borrowed reference,也就是不增加引用计数。例如,
PyList_GetItem
函数返回的就是 borrowed reference。如果需要长期持有这个对象,需要调用Py_INCREF
增加引用计数,用完后调用Py_DECREF
减少引用计数。
第九部分:一些建议
-
尽量使用现有的库
如果已经有现成的C/C++库实现了你需要的功能,尽量使用它们,而不是自己重新编写。
-
保持C代码的简洁
C代码应该尽量简洁、易懂。不要过度优化,避免使用复杂的技巧。
-
编写测试用例
为C扩展编写测试用例,确保其功能正确。
-
使用代码分析工具
使用代码分析工具,例如Valgrind,检查内存泄漏和错误。
总结
C扩展是Python的一项高级技术,可以显著提高Python程序的性能。但是,C扩展的编写比较复杂,需要掌握C语言和Python C API。希望通过今天的讲座,大家对C扩展有了更深入的了解。 以后有机会再给大家分享更多关于Python高级技术的知识。 感谢大家的聆听!