转)使用C/C++扩展Python
转)使用C/C++扩展Python
目录(?)[+]
使用C/C++扩展Python
翻译: |
gashero |
---|
如果你会用C,实现Python嵌入模块很简单。利用扩展模块可做很多Python不方便做的事情,他们可以直接调用C库和系统调用。
为了支持扩展,Python API定义了一系列函数、宏和变量,提供了对Python运行时系统的访问支持。Python的C API由C源码组成,并包含 “Python.h” 头文件。
编写扩展模块与你的系统相关,下面会详解。
目录
1 一个简单的例子
2 关于错误和异常
3 回到例子
4 模块方法表和初始化函数
5 编译和连接
6 在C中调用Python函数
7 解析传给扩展模块函数的参数
8 解析传给扩展模块函数的关键字参数
9 构造任意值
10 引用计数
10.1 Python中的引用计数
10.2 拥有规则
10.3 危险的薄冰
10.4 NULL指针
11 使用C++编写扩展
12 提供给其他模块以C API
1 一个简单的例子
下面的例子创建一个叫做 “spam” 的扩展模块,调用C库函数 system() 。这个函数输入一个NULL结尾的字符串并返回整数,可供Python调用方式如下:
>>> import spam >>> status=spam.system("ls -l")
一个C扩展模块的文件名可以直接是 模块名.c 或者是 模块名module.c 。第一行应该导入头文件:
#include <Python.h>
因为Python含有一些预处理定义,所以你必须在所有非标准头文件导入之前导入Python.h 。
下面添加C代码到扩展模块,当调用 “spam.system(string)” 时会做出响应:
static PyObject* spam_system(PyObject* self, PyObject* args) {const char* command;int sts;if (!PyArg_ParseTuple(args,"s",&command))return NULL;sts=system(command);return Py_BuildValue("i",sts); }
调用方的Python只有一个命令参数字符串传递到C函数。C函数总是有两个参数,按照惯例分别叫做 self 和 args 。
self 参数仅用于用C实现内置方法而不是函数。本例中, self 总是为NULL,因为我们定义的是个函数,不是方法。这一切都是相同的,所以解释器也就不需要刻意区分两种不同的C函数。
PyArg_ParseTuple() 正常返回非零,并已经按照提供的地址存入了各个变量值。如果出错(零)则应该让函数返回NULL以通知解释器出错。
2 关于错误和异常
有一种情况下,模块可能依靠其他 PyErr_*() 函数给出更加详细的错误信息,并且是正确的。但是按照一般规则,这并不重要,很多操作都会因为种种原因而挂掉。
想要忽略这些函数设置的异常,异常情况必须明确的使用 PyErr_Clear() 来清除。只有在C代码想要自己处理异常而不是传给解释器时才这么做。
还要注意的是 PyArg_ParseTuple() 系列函数的异常,返回一个整数状态码是有效的,0是成功,-1是失败,有如Unix系统调用。
最后,小心垃圾情理,也就是 Py_XDECREF() 和 Py_DECREF() 的调用,会返回的异常。
你也可以为你的模块定义一个唯一的新异常。需要在文件前部声明一个静态对象变量,如:
static PyObject* SpamError;
然后在模块初始化函数(initspam())里面初始化它,并省却了处理:
PyMODINIT_FUNC initspam(void) {PyObject* m;m=Py_InitModule("spam",SpamMethods);if (m==NULL)return NULL;SpamError=PyErr_NewException("spam.error",NULL,NULL);Py_INCREF(SpamError);PyModule_AddObject(m,"error",SpamError); }
注意实际的Python异常名字是 spam.error 。 PyErr_NewException() 函数使用Exception为基类创建一个类(除非是使用另外一个类替代NULL)。
同样注意的是创建类保存了SpamError的一个引用,这是有意的。为了防止被垃圾回收掉,否则SpamError随时会成为野指针。
一会讨论 PyMODINIT_FUNC 作为函数返回类型的用法。
3 回到例子
if (!PyArg_ParseTuple(args,"s",&command))return NULL;
就是为了报告解释器一个异常。如果执行正常则变量会拷贝到本地,后面的变量都应该以指针的方式提供,以方便设置变量。本例中的command会被声明为 “const char* command” 。
下一个语句使用UNIX系统函数system(),传递给他的参数是刚才从 PyArg_ParseTuple() 取出的:
sts=system(command);
return Py_BuildValue("i",sts);
在这种情况下,会返回一个整数对象,这个对象会在Python堆里面管理。
如果你的C函数没有有用的返回值,则必须返回None。你可以用 Py_RETUN_NONE 宏来完成:
Py_INCREF(Py_None); return Py_None;
Py_None 是一个C名字指定Python对象None。这是一个真正的PY对象,而不是NULL指针。
4 模块方法表和初始化函数
static PyMethodDef SpamMethods[]= {...{"system",spam_system,METH_VARARGS,"Execute a shell command."},...{NULL,NULL,0,NULL} /*必须的结束符*/ };
如果单独使用 METH_VARARGS ,函数会等待Python传来tuple格式的参数,并最终使用 PyArg_ParseTuple() 进行解析。
方法表必须传递给模块初始化函数。初始化函数函数名规则为 initname() ,其中 name 为模块名。并且不能定义为文件中的static函数:
PyMODINIT_FUNC initspam(void) {(void) Py_InitModule("spam",SpamMethods); }
注意 PyMODINIT_FUNC 声明了void为返回类型,还有就是平台相关的一些定义,如C++的就要定义成 extern “C” 。
int main(int argc, char* argv[]) {Py_SetProgramName(argv[0]);Py_Initialize();initspam();//... }
在Python发行版的 Demo/embed/demo.c 中有可以参考的源码。
5 编译和连接
如果使用动态载入,细节依赖于系统,查看关于构建扩展模块部分,和关于在Windows下构建扩展的细节。
spam spammodule.o
如果你的模块需要其他扩展模块连接,则需要在配置文件后面加入,如:
spam spammodule.o -lX11
6 在C中调用Python函数
迄今为止,我们一直把注意力集中于让Python调用C函数,其实反过来也很有用,就是用C调用Python函数。这在回调函数中尤其有用。如果一个C接口使用回调,那么就要实现这个回调机制。
static PyObject* my_callback=NULL; static PyObject* my_set_callback(PyObject* dummy, PyObject* args) {PyObject* result=NULL;PyObject* temp;if (PyArg_ParseTuple(args,"O:set_callback",&temp)) {if (!PyCallable_Check(temp)) {PyErr_SetString(PyExc_TypeError,"parameter must be callable");return NULL;}Py_XINCREF(temp);Py_XINCREF(my_callback);my_callback=temp;Py_INCREF(Py_None);result=Py_None;}return result; }
这个函数必须使用 METH_VARARGS 标志注册到解释器。宏 Py_XINCREF() 和 Py_XDECREF() 增加和减少对象的引用计数。
int arg; PyObject* arglist; PyObject* result; //... arg=123; //... arglist=Py_BuildValue("(i)",arg); result=PyEval_CallObject(my_callback,arglist); Py_DECREF(arglist);
if (result==NULL)return NULL; /*向上传递异常*/ //使用result Py_DECREF(result);
PyObject* arglist; //... arglist=Py_BuildValue("(l)",eventcode); result=PyEval_CallObject(my_callback,arglist); Py_DECREF(arglist); if (result==NULL)return NULL; /*一个错误*/ /*使用返回值*/ Py_DECREF(result);
注意 Py_DECREF(arglist) 所在处会立即调用,在错误检查之前。当然还要注意一些常规的错误,比如 Py_BuildValue() 可能会遭遇内存不足等等。
7 解析传给扩展模块函数的参数
int PyArg_ParseTuple(PyObject* arg, char* format, ...);
注意 PyArg_ParseTuple() 会检测他需要的Python参数类型,却无法检测传递给他的C变量地址,如果这里出错了,可能会在内存中随机写入东西,小心。
任何Python对象的引用,在调用者这里都是 借用的引用 ,而不增加引用计数。
int ok; int i,j; long k,l; const char* s; int size; ok=PyArg_ParseTuple(args,""); /* python call: f() */ok=PyArg_ParseTuple(args,"s",&s); /* python call: f('whoops!') */ok=PyArg_ParseTuple(args,"lls",&k,&l,&s); /* python call: f(1,2,'three') */ok=PyArg_ParseTuple(args,"(ii)s#",&i,&j,&s,&size); /* python call: f((1,2),'three') */{const char* file;const char* mode="r";int bufsize=0;ok=PyArg_ParseTuple(args,"s|si",&file,&mode,&bufsize);/* python call:f('spam')f('spam','w')f('spam','wb',100000)*/ }{int left,top,right,bottom,h,v;ok=PyArg_ParseTuple(args,"((ii)(ii))(ii)",&left,&top,&right,&bottom,&h,&v);/* python call: f(((0,0),(400,300)),(10,10)) */ }{Py_complex c;ok=PyArg_ParseTuple(args,"D:myfunction",&c);/* python call: myfunction(1+2j) */ }
8 解析传给扩展模块函数的关键字参数
函数 PyArg_ParseTupleAndKeywords() 声明如下:
int PyArg_ParseTupleAndKeywords(PyObject* arg, PyObject* kwdict, char* format, char* kwlist[],...);
嵌套的tuple在使用关键字参数时无法生效,不在kwlist中的关键字参数会导致 TypeError 异常。
如下是使用关键字参数的例子模块,作者是 Geoff Philbrick (phibrick@hks.com):
#include "Python.h"static PyObject* keywdarg_parrot(PyObject* self, PyObject* args, PyObject* keywds) {int voltage;char* state="a stiff";char* action="voom";char* type="Norwegian Blue";static char* kwlist[]={"voltage","state","action","type",NULL};if (!PyArg_ParseTupleAndKeywords(args,keywds,"i|sss",kwlist,&voltage,&state,&action,&type))return NULL;printf("-- This parrot wouldn't %s if you put %i Volts through it.n",action,voltage);printf("-- Lovely plumage, the %s -- It's %s!n",type,state);Py_INCREF(Py_None);return Py_None; }static PyMethodDef keywdary_methods[]= {/*注意PyCFunction,这对需要关键字参数的函数很必要*/{"parrot",(PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,"Print a lovely skit to standard output."},{NULL,NULL,0,NULL} };void initkeywdarg(void) {Py_InitModule("keywdarg",keywdarg_methods); }
9 构造任意值
这个函数声明与 PyArg_ParseTuple() 很相似,如下:
PyObject* Py_BuildValue(char* format, ...);
接受一个格式字符串,与 PyArg_ParseTuple() 相同,但是参数必须是原变量的地址指针。最终返回一个Python对象适合于返回给Python代码。
代码 | 返回值 |
Py_BuildValue(”") | None |
Py_BuildValue(”i”,123) | 123 |
Py_BuildValue(”iii”,123,456,789) | (123,456,789) |
Py_BuildValue(”s”,”hello”) | ‘hello’ |
Py_BuildValue(”ss”,”hello”,”world”) | (’hello’, ‘world’) |
Py_BuildValue(”s#”,”hello”,4) | ‘hell’ |
Py_BuildValue(”()”) | () |
Py_BuildValue(”(i)”,123) | (123,) |
Py_BuildValue(”(ii)”,123,456) | (123,456) |
Py_BuildValue(”(i,i)”,123,456) | (123,456) |
Py_BuildValue(”[i,i]”,123,456) | [123,456] |
Py_BuildValue(”{s:i,s:i}”,’a',1,’b',2) | {’a':1,’b':2} |
Py_BuildValue(”((ii)(ii))(ii)”,1,2,3,4,5,6) | (((1,2),(3,4)),(5,6)) |
10 引用计数
在C/C++语言中,程序员负责动态分配和回收堆(heap)当中的内存。这意味着,我们在C中编程时必须面对这个问题。
10.1 Python中的引用计数
同样重要的一个概念是 借用 一个对象,借用的对象不能调用 Py_DECREF() 来减少引用计数。借用者在不需要借用时,不保留其引用就可以了。应该避免拥有者释放对象之后仍然访问对象,也就是野指针。
借用的优点是你无需管理引用计数,缺点是可能被野指针搞的头晕。借用导致的野指针问题常发生在看起来无比正确,但是事实上已经被释放的对象。
借用的引用也可以用 Py_INCREF() 来改造成拥有的引用。这对引用的对象本身没什么影响,但是拥有引用的程序有责任在适当的时候释放这个拥有。
10.2 拥有规则
函数 PyImport_AddModule() 也是返回借用的引用,尽管他实际上创建了对象,只不过其拥有的引用实际存储在了 sys.modules 中。
当一个C函数被py调用时,使用对参数的借用。调用者拥有参数对象的拥有引用。所以,借用的引用的寿命是函数返回。只有当这类参数必须存储时,才会使用 Py_INCREF() 变为拥有的引用。
从C函数返回的对象引用必须是拥有的引用,这时的拥有者是调用者。
10.3 危险的薄冰
有些使用借用的情况会出现问题。这是对解释器的盲目理解所导致的,因为拥有者往往提前释放了引用。
首先而最重要的情况是使用 Py_DECREF() 来释放一个本来是借用的对象,比如列表中的元素:
void bug(PyObject* list) {PyObject* item=PyList_GetItem(list,0);PyList_SetItem(list,1,PyInt_FromLong(0L));PyObject_Print(item,stdout,0); /* BUG! */ }
这个函数首先借用了 list[0] ,然后把 list[1] 替换为值0,最后打印借用的引用。看起来正确么,不是!
因为使用python编写,所以 __del__() 中可以用任何python代码来完成释放工作。替换元素的过程会执行 del list[0] ,即减掉了对象的最后一个引用,然后就可以释放内存了。
知道问题后,解决方案就出来了:临时增加引用计数。正确的版本如下:
void no_bug(PyObject* list) {PyObject* item=PyList_GetItem(list,0);Py_INCREF(item);PyList_SetItem(list,1,PyInt_FromLong(0L));PyObject_Print(item,stdout,0);Py_DECREF(item); }
这是一个真实的故事,旧版本的Python中多处包含这个问题,让guido花费大量时间研究 __del__() 为什么失败了。
void bug(PyObject* list) {PyObject* item=PyList_GetItem(list,0);Py_BEGIN_ALLOW_THREADS//一些IO阻塞调用Py_END_ALLOW_THREADSPyObject_Print(item,stdout,0); /*BUG*/ }
10.4 NULL指针
最好的测试NULL的方法就是在代码里面,一个指针如果收到了NULL,例如 malloc() 或其他函数,则表示发生了异常。
宏 Py_INCREF() 和 Py_DECREF() 并不检查NULL指针,不过还好, Py_XINCREF() 和 Py_XDECREF() 会检查。
检查特定类型的宏,形如 Pytype_Check() 也不检查NULL指针,因为这个检查是多余的。
C函数的调用机制确保传递的参数列表(也就是args参数)用不为NULL,事实上,它总是一个tuple。
而把NULL扔到Python用户那里可就是一个非常严重的错误了。
11 使用C++编写扩展
12 提供给其他模块以C API
函数 PySpam_System() 是一个纯C函数,声明为static如下:
static int PySpam_System(const char* command) {return system(command); }
static PyObject* spam_system(PyObject* self, PyObject* args) {const char* command;int sts;if (!PyArg_ParseTuple(args,"s",&command))return NULL;sts=PySpam_System(command);return Py_BuildValue("i",sts); }
#include "Python.h"
#define SPAM_MODULE #include "spammodule.h"
这个宏定义是告诉头文件需要作为导出模块,而不是客户端模块。最终模块的初始化函数必须管理初始化C API指针数组的初始化:
PyMODINIT_FUNC initspam(void) {PyObject *m;static void *PySpam_API[PySpam_API_pointers];PyObject *c_api_object;m = Py_InitModule("spam", SpamMethods);if (m == NULL)return;/* Initialize the C API pointer array */PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;/* Create a CObject containing the API pointer array's address */c_api_object = PyCObject_FromVoidPtr((void *)PySpam_API, NULL);if (c_api_object != NULL)PyModule_AddObject(m, "_C_API", c_api_object); }
注意 PySpam_API 声明为static,否则 initspam() 函数执行之后,指针数组就消失了。
大部分的工作还是在头文件 spammodule.h 中,如下:
#ifndef Py_SPAMMODULE_H #define Py_SPAMMODULE_H #ifdef __cplusplus extern "C" { #endif/* Header file for spammodule *//* C API functions */ #define PySpam_System_NUM 0 #define PySpam_System_RETURN int #define PySpam_System_PROTO (const char *command)/* Total number of C API pointers */ #define PySpam_API_pointers 1#ifdef SPAM_MODULE /* This section is used when compiling spammodule.c */static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;#else /* This section is used in modules that use spammodule's API */static void **PySpam_API;#define PySpam_System (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])/* Return -1 and set exception on error, 0 on success. */ static int import_spam(void) {PyObject *module = PyImport_ImportModule("spam");if (module != NULL) {PyObject *c_api_object = PyObject_GetAttrString(module, "_C_API");if (c_api_object == NULL)return -1;if (PyCObject_Check(c_api_object))PySpam_API = (void **)PyCObject_AsVoidPtr(c_api_object);Py_DECREF(c_api_object);}return 0; }#endif#ifdef __cplusplus } #endif#endif /* !defined(Py_SPAMMODULE_H) */
想要调用 PySpam_System() 的客户端模块必须在初始化函数中调用 import_spam() 以初始化导出扩展模块:
PyMODINIT_FUNC initclient(void) {PyObject* m;m=Py_InitModule("client",ClientMethods);if (m==NULL)return;if (import_spam()<0)return;/*其他初始化语句*/ }
转)使用C/C++扩展Python相关推荐
- 用C语言扩展Python的功能的实例
用C语言扩展Python的功能的实例 分类: C/C++ 编程技巧 Programes 2008-04-23 09:31 1232人阅读 评论(0)收藏 举报 python扩展语言cmethodsnu ...
- python c 混合编程 用c循环_混合编程:用 C 语言来扩展 Python 大法吧!
Python 实在是一种让人上瘾的编程语言,简洁的语法+丰富的扩展包,几乎可以用 Python 做任何事情,唯一的黑点似乎就是「慢」,但是与高效的编译语言 C\C++ 互联以后,可以解决脚本语言运行速 ...
- python 异常处理模块_扩展Python模块系列(五)----异常和错误处理
在上一节中,讨论了在用C语言扩展Python模块时,应该如何处理无处不在的引用计数问题.重点关注的是在实现一个C Python的函数时,对于一个PyObject对象,何时调用Py_INCREF和Py_ ...
- python get()函数_C++使用ffpython嵌入和扩展python(python2和python3)
C++使用ffpython嵌入和扩展python(python2和python3) 摘要: 在服务器编程中,经常会用到python脚本技术.Python是最流行的脚本之一,并且python拥有定义良好 ...
- 第十七章 扩展Python
第十七章 Python什么都能做,真的是这样.这门语言功能强大,但有时候速度有点慢. 鱼和熊掌兼得 本章讨论确实需要进一步提升速度的情形.在这种情况下,最佳的解决方案可能不是完全转向C语言(或其他中低 ...
- python学习(十七) 扩展python
c, c++, java比python快几个数量级. 17.1 考虑哪个更重要 开发速度还是运行速度更重要. 17.2 非常简单的途径:Jython和IronPython Jython可以直接访问JA ...
- 扩展Python模块系列(二)----一个简单的例子
本节使用一个简单的例子引出Python C/C++ API的详细使用方法.针对的是CPython的解释器. 目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两 ...
- python字符串无效的原因_Python(60)扩展和嵌入1.使用 C 或 C++ 扩展 Python
1. 使用 C 或 C++ 扩展 Python 如果你会用 C,添加新的 Python 内置模块会很简单.以下两件不能用 Python 直接做的事,可以通过 extension modules 来实现 ...
- python zope 工作流_使用C语言来扩展Python程序和Zope服务器的教程
有几个原因使您可能想用 C 扩展 Zope.最可能的是您有一个已能帮您做些事的现成的 C 库,但是您对把它转换成 Python 却不感兴趣.此外,由于 Python 是解释性语言,所以任何被大量调用的 ...
最新文章
- cgi标准面试php,PHP面试:简述CGI、FastCGI和PHP
- 自然语言处理笔记4-哈工大 关毅
- web加载本地html,WKWebview加载本地html问题汇总
- 基础知识—函数-默然参数
- centos locate搜索工具
- 单片机实验报告实验七:定时器实验
- JetBrains 提供面向开发人员的免费开源字体 Mono
- 难道真的是RPWT-LFS日记1
- 华为网络设备——利用三层交换机实现VLAN间路由配置实例
- npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features
- Android应用调用第三方地图应用导航
- vmware虚拟机扩展磁盘空间
- HDU1880——哈希表(BKDR算法)——魔咒词典
- JQuery EasyUI 结合ztrIee的后台页面开发
- 第14届军警狙击手世界杯:中国队包揽全部冠军
- 计算菲波那切数列前50项
- 嵌入式linux系统应用开发
- 服务器运行时将杀毒软件关掉,教你快速关闭禁用Windows 10自带的杀毒软件Windows Defender...
- python实现食品推荐_通过Python语言实现美团美食商家数据抓取
- Oracle EBS 付款后无法创建会计科目