2022年 11月 3日

[Python] 扩展程序

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

参考

  1. Python核心编程(第3版)