Python的C语言扩展学习
文章目录
- Python的C语言扩展学习
-
- 1. 什么情况需要扩展python
- 2. 扩展python的缺点
- 3. python扩展主要过程
-
- 3.1 大体过程:
- 3.2 用例
-
- 创建应用代码
- 编写封装程序
- 编译
- 参考
注:主要用C语言作为例子扩展python程序
1. 什么情况需要扩展python
1)需要 Python 没有的额外功能:比如像创建新的数据类型或在已有应用中嵌入 Python,就必须使用编译后的模块。
2)改善性能:用C或者C++这样运行性能高的语言执行一些耗时程序;但是需要注意的是,我们在用扩展程序对性能优化时,需要找到真正的瓶颈代码,然后用C\C++语言去优化这些瓶颈代码,使其整体运行性能提升。
3)隐藏专有代码:因为python这种易用语言私密性不好,因而,为了保证一些核心代码私有性,用这种扩展的方式,对核心代码的“封装”。因为这些C\C++语言扩展程序是已编译成二进制文件共python调用的,因而安全。
2. 扩展python的缺点
- 必须编写 C/C++代码。
- 需要理解如何在 Python 和 C/C++之间传递数据。
- 需要手动管理引用
3. python扩展主要过程
3.1 大体过程:
1. 创建应用代码:也就是编写对应的C\C++程序;
2. 根据样板编写封装代码:也就是将编写的C\C++程序,封装成:
– 接收python数据类型的数据
– 然后转化为C\C++数据类型数据
– 然后执行C\C++程序
– 将返回结构转化为python数据类型。
的封装函数,用于python调用的接口。
3. 编译\测试: 创建setup,将模块导入python环境下;测试程序的正确性
3.2 用例
创建应用代码
/*myextest.h**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int fac(int n);
char * reverse(char*s);
int test(void);
/* myextest.c */
#include "myextest.h"
// 阶乘函数
int fac(int n){
if (n<2) return (1); /* 0!= 1! == 1*/
return (n) * fac(n-1);
}
// 字符串翻转函数
char * reverse(char*s)
{
register char t, // temp
*p = s,
*q = (s + (strlen(s) -1));
while(p < q)
{
t = *p;
*p++ = *q;
*q-- = t;
}
return s;
}
int main(void){ // 需要调试好,确保扩展程序的正确性。
char s[BUFSIZ];
printf("4!==%d\n", fac(4));
strcpy(s,"abcdef");
printf("reversing 'abcdef', we get '%s'\n", \
reverse(s));
return 0;
}// 再次强调,必须尽可能先完善扩展程序的代码。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
编写封装程序
样板代码主要含有四部分。
1.包含 Python 头文件。
2.为每一个模块函数添加形如 PyObject*Module_func()的封装函数。
3.为每一个模块函数添加一个 PyMethodDef ModuleMethods[]数组/表。
4. 添加模块初始化函数 void initModule()。
/*mywrapper.c*/
#include "Python.h" //要做的是找到 Python 包含文件,并确保编译器可以访问这个文件的目录
#include "myextest.h" 头文件,声明,否则fac和reverse无法被调用
/*说明:
为函数编写形如 PyObject* Module_func()的封装函数
1)需要创建以static PyObject*为返回值的函数
2)函数名必须以模块名开头,紧接着是下划线和函数名本身的函数,注意如果封装函数名字为MyEXT_myfunction();那么python调用时,
from MyEXT import myfunction
怎样才能完成这种转换?答案是在从 Python 到 C 时,调用一系列的PyArg_Parse*()函数,从 C 返回 Python 时,调用 Py_BuildValue()函数。
这些 PyArg_Parse*()函数与 C 中的 sscanf()函数类似。其接受一个字节流,然后根据一些格式字符串进行解析,将结果放入到相应指针所指的变量中。 若解析成功就返回 1; 否则返回 0。
Py_BuildValue()的工作方式类似 sprintf(), 接受一个格式字符串,并将所有参数按照格式字符串指定的格式转换为一个 Python 对象。
*/
static PyObject *
_ext_fac(PyObject*self, PyObject*args){
int num;
if (!PyArg_ParseTuple(args,"i",&num))
return NULL;
return (PyObject*) Py_BuildValue("i",fac(num));
}
static PyObject*
_ext_reverse_str(PyObject*self, PyObject *args){
char* orig_str;
char* dupe_str;
PyObject * retval; // return to python
if (!PyArg_ParseTuple(args,"s",&orig_str))
return NULL;
//strdup是拷贝一个str, reverse是原地操作,因而为了保留orig_str,需要拷贝后再操作
retval = (PyObject*)Py_BuildValue("ss", orig_str,dupe_str=reverse(strdup(orig_str))); //此处有拷贝操作,因此需要最后释放原始空间,否则有内存泄露问题。
free(dupe_str);
return retval;
}
static PyObject*
_ext_test(PyObject*self, PyObject *args){
test();
/* 当创建扩展时,必须额外注意如何处理 Python 对象,必须留心是否
需要修改此类对象的引用计数*/
Py_INCREF(Py_None);
return PyNone; //(PyObject*) Py_BuildValue("");
}
/* 为模块编写 PyMethodDef ModuleMethods[]数组
既然两个封装函数都已完成,下一步就需要在某个地方将函数列出来,以便让 Python 解释器知道如何导入并访问这些函数。这就是 ModuleMethods[]数组的任务。
也就是将封装后的函数与python对接,使其能在python中调用
*/
static PyMethodDef
_extMethods[] =
{
// {python中访问所用的名称,对应的封装函数,参数以什么形式给定}
{"fac",_ext_fac,METH_VARARGS}, //METH_VARARGS表示以元组形式给定
{"reverse_str",_ext_reverse_str,METH_VARARGS},
{"test",_ext_test,METH_VARARGS},
{NULL, NULL},
};
/** 添加模块初始化函数 void initModule()
最后一部分是模块初始化函数。当解释器导入模块时会调用这段代码。这段代码中只调用了 Py_InitModule()函数,其第一个参数是模块名称,第二个是ModuleMethods[]数组,这样解释器就可以访问模块函数。
*/
void init_ext(void){
Py_InitModule("_ext",_extMethods); //初始化所有封装的模块
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
编译
1. 创建 setup.py。
2. 运行 setup.py 来编译并链接代码。
3.在 Python 中导入模块。
#!/usr/bin/env python
/*现在使用distutils 包来构建、安装和发布模块、扩展和软件包*/
from distutils.core import setup, Extension
Mod = '_ext'
ext_modules = [Extension(Mod,sources=['myextest.c','mywrapper.c'])] //注意此处没有.h文件
setup(name=Mod,ext_modules=ext_modules)
- 1
- 2
- 3
- 4
- 5
- 6
编译: python setup.py build,这样可切换到这个目录中进行调用
导入:要么用 python setup.py install导入包,即将其安装到python中,任意目录下使用
from _ext import fac, reverse_str
- 1
- 2
参考
- Python核心编程(第3版)