2022年 11月 5日

Python3基础–17–Python扩展

一、扩展理论基础

概念:
任何可以集成或导入另一个Python脚本的代码都是一个扩展
举例:将C代码封装进Python中

什么时候需要扩展python:
1、需要python没有的额外功能
2、改善瓶颈性能。把软件开发过程中的瓶颈部分在扩展中实现
3、隐藏专有代码。实现自我研究成果保护

什么情况下不应该扩展python:
1、必须编写c/c++代码
2、需要手动管理应用

二、扩展代码编写

编写python扩展的3个步骤

2.1、创建应用代码:Extest1.c

所有需要成为扩展的代码应组成一个独立的库,这些代码将作为一个Python模块存在。创建测试代码来保证代码的正确性

#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
  
int fac(int n)  	                      //递归阶乘
{  
	if (n < 2) return (1); 
	return (n) * fac(n-1); 
}  

char *reverse(char *s)                    //字符串逆序
{  
	register char t,  *p = s, *q = (s + (strlen(s)-1));  
	while (p < q)  
	{  
		t = *p;  
		*p++ = *q;  
		*q-- = t;  
	}  
	return s;  
}  

int main()  
{  
	char s[BUFSIZ];  
	printf("4! == %d\n", fac(4));  
	printf("8! == %d\n", fac(8));  
	printf("12! == %d\n", fac(12));  
	strcpy(s, "abcdef");  
	printf("reversing 'abcdef', we get '%s'\n", reverse(s));  
	strcpy(s, "madam");  
	printf("reversing 'madam', 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

2.2、根据样板编写封装代码

样板代码主要有四部分
1、包含python头文件。#include "Python.h"
2、为每一个模块函数添加形如PyObject* Module_func()的封装函数
—-需要为所有想被Python环境访问的函数增加一个静态函数,函数返回类型是PyObject*,函数名前要加上模块名和一个下划线。从后面来看就是,与普通的Python模块导入没有什么差别,即在Python解释器中能先import Extest,再通过Extest.fac()来访问上述C代码中的fac()函数。
—-包装函数的用处就是,先把Python的值传给C,然后调用相关函数,得到相应的结果,再把这个结果转换成Python对象,传回给Python。从Python到C,要用到PyArg_Parse*()函数,从C到Python要用到Py_BuildValue()函数。
——–a、PyArg_Parse*()函数接受一个字符串流,并根据一个指定的格式字符串进行解析,把结果放入到相应的指针所指的变量中。若返回1,表示解析成功,返回0,表示解析失败。
——–b、Py_BuildValue(),把所有的参数按格式字符串所指定的格式转换成一个Python对象

方法 解释
int PyArg_ParseTuple() 把Python传过来的参数转为C
int PyArg_ParseTupleAndKeyWords() 同上,但同时解析关键字参数
Py_BuildValue() 把C的数据转为Python的一个或一组对象,然后返回

3、为每一个模块函数添加一个PyMethodDef ModuleMethods[]数组/表
4、添加模块初始化函数void initModule()

#include<stdio.h>  
#include<stdlib.h>  
#include<string.h>  
  
int fac(int n)  
{  
	if (n < 2) return (1);  
	return (n) * fac(n-1);   
}

char *reverse(char *s)  
{  
	register char t,  *p = s,  *q = (s + (strlen(s)-1));  
	while (s &&(p < q))  
	{  
		t = *p;  
		*p++ = *q;  
		*q-- = t;  
	}  
	return s;  
}  

int test()  
{  
	char s[BUFSIZ];  
	printf("4! == %d\n", fac(4));  
	printf("8! == %d\n", fac(8));  
	printf("12! == %d\n", fac(12));  
	strcpy(s, "abcdef");  
	printf("reversing 'abcdef', we get '%s'\n", reverse(s));  
	strcpy(s, "madam");  
	printf("reversing 'madam', we get '%s'\n", reverse(s));  
	return 0;  
} 
 
#include "Python.h"                                   //Python头文件  
  
//两个包装函数Extest_fac和Extest_doppel  
static PyObject *  
Extest_fac(PyObject *self, PyObject *args)  
{  
	int num;                                          //解析结果  
	if (!PyArg_ParseTuple(args, "i", &num))           //PyArg_ParseTuple(args, "i", &num)就是将传入的数据解析,i表示希望得到整型变量,如果传入正确,将它保存到num变量中  
		return NULL;                                  //如果传入错误,返回NULL  
	return (PyObject*)Py_BuildValue("i", fac(num));   //直接将结果转为Python的整型返回  
}  
static PyObject *  
Extest_doppel(PyObject *self, PyObject *args)  
{  
	char *orig_str;                                   //原始字符串  
	char *dupe_str;                                   //反转后的字符串  
	PyObject* retval;  
	if (!PyArg_ParseTuple(args, "s", &orig_str))  
		return NULL;  
	retval = (PyObject*)Py_BuildValue("ss", orig_str, dupe_str=reverse(strdup(orig_str)));                 
	//'ss'就表示返回一个含有两个字符串的元组,strdup是复制字符串,要复制一份原始字符串

	free(dupe_str);                                  //释放内存  
	return retval;  
}  

//测试函数,将Extest1中的main()改为test()  
static PyObject *  
Extest_test(PyObject *self, PyObject *args)  
{  
	test();  
	return (PyObject*)Py_BuildValue("");             //返回空字符串给Python  
}  

//把包装函数列在某个地方,以便Python解释器能够导入并调用  
static PyMethodDef  
ExtestMethods[] =                                   //该数组由多个二维数组组成,每个数组包含一个函数的信息,最后的NULL表示列表的结束
{  
	{"fac", Extest_fac, METH_VARARGS },             //第一项是函数在Python中的名字,第二项是相应的包装函数的名字,第三项是METH_VARARGS常量,该常量表示参数以元组形式传入  
	{"doppel", Extest_doppel, METH_VARARGS },  
	{"test", Extest_test, METH_VARARGS },  
	{NULL, NULL },  
};  

//模块初始化函数void initModule(),这部分代码在模块导入时被解释器调用  
void initExtest()  
{  
	Py_InitModule("Extest", ExtestMethods);  
}  
  • 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
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

2.3、编译并测试(此处只解读编译)

1、创建setup.py
2、运行setup.py来编译并链接代码
3、在python中导入模块
4、添加测试函数

#! /usr/bin/python
#-*-coding: utf-8-*-  

from distutils.core import setup, Extension  
MOD = 'Extest'  
setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])   
#在编译扩展之前,需为每一个扩展创建Extension实例,本例中是'Extest',后面跟的sources是所有源代码的文件列表,本例中是'Extest2.c'  
#setup()需两个参数,一个是名字参数,表示要编译哪个东西,另外一个是列表,列出要编译的对象
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8