Python学习之路(5)— 使用C扩展
Python学习之路(5)— 使用C扩展
一、前言
参考:https://www.cnblogs.com/yinguo/p/4641349.html
Python C扩展是指用C语言编写的代码,然后编译成Python可以调用的库。这样可以提高Python代码的执行效率,或者实现某些Python无法直接实现的功能。
开发平台: Ubuntu 20.04.6 LTS
二、引入 Python.h 头文件
首先编写c扩展需要引入Python.h头文件,以ubuntu20为例,该文件在如下路径:
/usr/include/python3.8/
如果没有,可以使用如下目录安装
sudo apt-get install python3-dev
我们后面在编译共享库的时候需要指定该路径
三、编写处理函数
新建 hello.c
文件,我们需要按照指定的格式编写函数,才可以让python调用;
函数一般声明成 static ,第一个参数是一个默认传入的 Python 对象,第二个参数是我们调用时传入的参数
static PyObject * hello_sum(PyObject *self, PyObject *args)
函数接受的参数是从 Python 环境下传入的,这和 C 中看到的函数是不同的,在 Python 的世界中,一切都是对象。所以,包装函数中首先要处理的问题就是解析从 Python 占获取的参数(实际上它是一个序列化后的字符串);
我们常用的处理参数的函数是: PyArg_ParseTuple
例如我们这里要解析两个整数,我们先定义两个整数,如何使用PyArg_ParseTuple
解析获取:
int a;
int b;
PyArg_ParseTuple(args, "i|i", &a, &a);
其中i|i
就表示要把传入的参数args
解析成两个整数, 怎样我们就获得了想要传入的两个整数a
和b
;
同样的,我们要把值返回到 Python 环境中,也需要经过一些处理才行,常用的函数是Py_BuildValue
,这个函数的用法和上一步中的 PyArg_ParseTuple 是一样的,但它们过程相反,Py_BuildValue 把 C 中的值按给定的格式格式化成 Python 需要的对象。
return Py_BuildValue("i", (a+b));
编写完整的hello_sum函数如下所示:
static PyObject * hello_sum(PyObject *self, PyObject *args) {
int a, b, sum;
if (!PyArg_ParseTuple(args, "ii", &a, &b))
return NULL;
sum = a + b;
return Py_BuildValue("i", sum);
}
四、定义模块
我们把上面的函数实现完成之后,我们需要定义一个模块并将其导出。
首先,我们要在一个类型为 PyMethodDef 的结构体中注册我们需要导出到 Python 中的函数:
static PyMethodDef ExtendMethods[] = {
{"sum", hello_sum, METH_VARARGS, "Compute sum of two integers."},
{NULL, NULL, 0, NULL}
};
这个PyMethodDef 结构体有四个成员:
- “sum”: 导出后在 Pyhton 中可见的方法名;
- hello_sum: 在C中实际调用的函数;
- METH_VARARGS: 表示传入方法的是普通参数,当然还可以处理关键词参数;
- 第四个是这个方法的注释。
然后我们构建一个PyModuleDef 类型的结构体,就是我们要定义的模块了
static struct PyModuleDef hellomodule = {
PyModuleDef_HEAD_INIT,
"hello", // 模块名
"A simple example of Python C extension", // 模块文档
-1, // 模块状态
ExtendMethods// 模块的方法列表
};
这个PyModuleDef 结构体的成员如下:
PyModuleDef_HEAD_INIT
:初始化头部信息,确保结构体的正确初始化,使其符合 Python C API 的要求;hello
:这个模块的名称,我们在python中使用的就是这个模块名称;- 第三个成员指明这个模块的文档,可以是
NULL
; - 第四个成员表示模块的每个解释器状态的大小,如果模块将状态保存在全局变量中,则为-1;
- 第五个成员就是我们刚才定义的这个模块的方法列表;
五、初始化模块
使用如下方式初始化模块,编写模块初始化函数如下所示,PyMODINIT_FUNC
被用来声明模块初始化函数的返回类型。模块初始化函数会被 Python 调用,用于注册扩展模块及其功能。模块初始化函数的名称通常是 PyInit_<module_name>
,其中 <module_name>
是扩展模块的名字。
PyMODINIT_FUNC PyInit_hello(void) {
return PyModule_Create(&hellomodule);
}
六、编译共享库
使用如下命令编译共享库
gcc -shared -o hello.so -fPIC hello.c -I/usr/include/python3.8
编译完成后我们可以在当前目录下看到名为hello.so
的共享库
七、 python运行
编写python代码如下所示:
import hello
print(hello.sum(4, 3))
运行结果如下所示
八、使用setup.py
上面我们通过gcc创建共享库的方式成功实现了C扩展,但是需要在共享库存在的目录下才可以调用使用;
Python提供了一个将C扩展安装到Python的site-packages目录下的方法,这样使得我们可以在任意目录的Python脚本中导入并使用这个C扩展模块。
编写setup.py如下所示:
from setuptools import setup, Extension
# 定义C扩展模块
module = Extension('hello', sources=['hello.c'])
# 设置和安装
setup(
name='HelloPackage',
version='1.0',
description='A simple hello package',
ext_modules=[module]
)
创建命令如下所示,使用 --user
选项会将包安装到用户目录中的 site-packages 目录,而不需要管理员权限。
python3 setup.py install --user
原文地址:https://blog.csdn.net/qq_38113006/article/details/144790041
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!