POP Article V2.0 设为主页
收藏本站
首 页交流论坛留 言
您现在的位置:首 页 >> Python 与 XML >> Python开发 >> 查看文章
《扩展和嵌入python解释器》1
作者:asmc51的专栏  来源:www.sqlite.com.cn  时间:2008-2-27  【 字体: 】 〖 双击滚屏 〗

摘要:

Python是一种解释型,面向对象的编程语言。本文描述如何使用CC+ +编写新模块来扩展Python解释器。扩展模块能够定义新的函数也可以定义新的对象类型及其方法。本文也描述了如何将Python解释器作为一种扩展语 言嵌入到另一个应用程序中。最后,本文演示了如何编译和连接扩展模块,使扩展模块可以动态的(在运行时)加载到解释器中,如果操作系统支持动态加载特性。

本文需要你具有Python的基础知识。关于Python语言的介绍,请参看 Python TutorialPython Reference Manual 提供了这门语言更规范的定义。 Python Library Reference 描述了现有的对象类型,函数和模块(包括内置的模块和用Python编写的模块),这些都扩大了这门语言的应用范围。

关于全部Python/C API的详细描述,请参看 Python/C API Reference Manual

 

1. CC++扩展Python 

如果你懂得如何用C编程,那么为Python添加新的内置模块是十分容易的。通过加入新的扩展模块 ,可以做两件用Python语言不能直接做的事:

实现新的内置对象类型

调用C语言库函数和执行系统调用

为支持扩展,Python API定义了一系列函数,宏和变量,可以通过这些访问大部分的Python运行时系统。通过包含头文件"Python.h" Python API被加入到C语言源代码中。

1.1 一个简单的例子

让我们创建一个名叫 "spam" (这是Python粉丝Monty喜爱的食物)的扩展模块 ,我们打算创建一个Python接口,使用C库函数 system().1.1 这个函数以一个空字符结尾(null-terminated)的字符串作为参数,并返回一个整型数,我们想这个功能够象下面这样从Python调用:

 

>>> import spam>>> status = spam.system("ls -l")

首先创建文件 spammodule.c. (一般地,如果模块名为"spam", C它的实现文件名应为 spammodule.c; 如果模块名很长, "spammify", 模块名可以为 spammify.c .)

我们文件的第一行应为:

 

#include <Python.h>

这样就引入了Python API(如果你喜欢,可以增加描述模块的目的和版权信息)。

 

警告: 由于Python可能定义了一些预处理定义,在一些系统上,这些预处理可能影响标准头文件,你 最好 在包含任何标准头文件前包含 Python.h文件。

除了那些定义在标准头文件中的用户可见的符号,其他所有在 Python.h文件中定义的用户可见符号有 "Py" or "PY"前缀。 为方便起见,也由于它们被Python解释器广泛使用, "Python.h"包含几个标准头文件 <stdio.h>, <string.h>, <errno.h>, and <stdlib.h> 。如果后几个头文件在你的系统上不存在,需要直接声明malloc(), free() realloc() malloc(), free() and realloc() 函数。

接着,我们继续向模块文件添加C函数,在Python表达式。 "spam.system(string) "被计算时,这个C函数将被调用:

 

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参数列表(如:单个表达式 "ls -l")到直接翻译为C函数参数。C函数总是有两个参数,一般命名为self args

self参数只有当C函数实现内置方法使用,而实现函数时不使用。在本例中,由于我们定义的是一个函数而不是方法,self 总是一个NULL 指针(这样做可以使解释器不必关心两种C函数的不同)

args 参数是一个指向包含参数的Python元组对象指针。元组的每个条目对应于调用参数列表的一个参数。参数是Python对象――为了在我们的C函数里使用他们,我们必须把它们转换为C值。Python API函数PyArg_ParseTuple() 检查参数类型并把它们转换为C值。它使用一个模板字符串来确定的参数类型和转换后的值存入C变量的类型,后面详述。

如果所有参数都是正确的类型,并且参数代表的各元素都被存储到传入地址的变量中,则PyArg_ParseTuple()函数返回为true(非零)。 如果无效的参数被传入,函数返回false()。在后面的例子中,PyArg_ParseTuple也产生一个恰当的异常,于是调用函数立刻返回 NULL (正如我们在例子中看到的)。

1.2 Intermezzo: 错误和异常

下面是整个Python解释器的一个重要惯例:当一个函数发生错误时,它应该设置一个异常状态并返回一个错误值(通常是NULL指针)。异常存储在解释器内部的一个静态的全局变量中;如果这个全局变量值为NULL,表示没有异常发生,第二个全局变量存储异常(raise的第二个参数)的相关值。第三个变量存储在Python代码中发生错误时堆栈反向跟踪数据。这三个变量是Python变量sys.exc_ type, sys.exc_value sys.exc_traceback(参看Python Library Referencesys模块部分)对应的C变量。了解这些变量对于理解错误如何传递是非常重要的。

Python API定义了一些函数用来设置异常的各种类型。

最重要的一个是PyErr_SetString()。它的参数是一个异常对象和一个C字符串。异常对象通常是一个象PyExc_ZeroDivisionError一样的预定义对象。C字符串指示错误的原因并被转换为Python字符串对象存储在异常的相关值’associated value’)中。

另一个有用的函数是PyErr_SetFromErrno(),它只带一个异常参数,并通过检查全局变量errno来构造相关值。最常用的函数是PyErr_SetObject(),它带两个对象参数:异常和异常的相关值。你不必Py_INCREF()对象传递给这些函数。

检测non-destructively检测异常是否被PyErr_Occurred()函数设置。此函数[1]返回当前异常对象或在没有异常产生时返回NULL。一般的,因为你可以通过函数返回值识别错误,所以不必调用PyErr_Occurred()函数去查看是否在一个函数调用中有错误产生。

当函数F调用另一个函数G,并且检测到后面的函数-G有错误,函数F应当返回一个错误值(通常为NULL-1)。函数F应该不调用PyEr_*()函数之一 ――其中一个已经在G中调用。F的调用者必须也返回一个错误通知它的调用者,也不需要调用PyEr_*()函数,依次类推――最详细的错误信息已经由最先探测到错误的函数报告了。一旦错误到达Python解释器的主循环,这将终止当前执行的Python代码,并试图寻找Python程序员指定的异常句柄。

(在有些条件下,模块能够通过调用PyEr_*()函数给出确切的错误消息,并且在这种情况下,这么做很好。然而,作为一般规则,这是没有必要的,并且会导致错误信息丢失:由于各种原因,大多数操作都是失败的。)

为了忽略由于函数调用失败而产生的异常,异常条件必须通过明确调用函数PyErr_Clear()来清除。仅当不想传递错误给解释器而想要完全自己处理错误时,C代码才调用PyErr_Clear()函数。(可能是由于尝试些别的东西,或装做没有错误发生)

每次调用malloc()失败必须进入异常-malloc()直接调用者必须调用PyErr_NoMemory()并返回指示它自己错误的标志。所有的对象创建函数(如:PyInt_FromLong())都已经这们做了,所以这条规则只和那些直接调用malloc()的函数相关。

还有一点,PyArg_ParseTuple()和相似函数产生的异常,带有整数状态返回值的函数都认为返回值-1表示失败,0或整数表示成功,就象UNIX系统调用一样。

最后,当你返回一个错误指示时,小心地清除垃圾(通过调用你创建的对象的Py_XDECREF() Py_ DECREF()函数)。

产生哪个异常完全由你自己选择。有一些预先声明的C对象处理所有内置Python异常,如PyExc_ZeroDivisionError:你可以直接使用。当然,你应合理明智地选择异常,不要使用PyExc_TypeError表示文件不能打开(而应该使用PyExc_IOError)。如果参数列表发生一些错误,函数PyArg_ParseTuple()通常产生PyExc_TypeError错误。如果你有的参数必须在特定范围内,或必须满足其他条件,应该使用PyExc_ValueError异常。

你也可以在自己的模块中定义独一无二的新异常。为此,你应该在你的文件开始处声明一个静态的对象变量:

static PyObject *SpamError;

并且在你的模块的初始化函数(initspam())中用一个异常对象初始化它(不考虑错误检查)。

 

PyMODINIT_FUNCinitspam(void){    PyObject *m;    m = Py_InitModule("spam", SpamMethods);    SpamError = PyErr_NewException("spam.error", NULL, NULL);    Py_INCREF(SpamError);    PyModule_AddObject(m, "error", SpamError);}

请注意:Python中异常的名称为spam.error.PyErr_NewException()函数可以从一个异常基类派生的新类(除非传入另一个类而不是NULL),《Python Library Reference》内置异常(Built-in Exceptions)中有详述。

 

还请注意:SpamError变量保持着新创建异常的引用;这是故意的!由于异常能够被外部代码从模块中删除,类的引用的拥有者必须确认引用对象没有被释放,导致SpamError成为野指针。产生异常的C代码一起内核转储或其他不可预料的后果。下面,我们将在这个例子中讨论把PyMODINIT_FUN作为函数的返回类型。



[1] 这个函数的返回值是这样的吗?

1.3 回到例子

回到我们的例子函数中,现在,你应该能够理解下面这些语句:

 

    if (!PyArg_ParseTuple(args, "s", &command))        return NULL;

根据是否由PyArg_ParseTuple()函数设置了异常,如果在参数列表中检测到错误(异常被设置), 则函数返回NULL(函数返回对象指针的错误标识)。否则,参数的字符串值已经被拷贝到本地变量 command中了。这是一个分配的指针,你不应修改指针指向的字符串 (就象在标准C中,变量command应该正确地声明为"const char *command ")。

下面的语句调用了UNIX函数system(),传递给函数我们从PyArg_ParseTuple() 得到的字符串

 

    sts = system(command);

我们的spam.system() 函数必须把sts的值作为Python对象返回。这可以通过Py_BuildValue()函数实现, 此函数有点象PyArg_ParseTuple()函数的反函数,这个函数带一个格式化字符串和任意个C值,并且返回一个Python对象。Py_BuildValue() 函数的详细信息后面给出。

 

    return Py_BuildValue("i", sts);

在本例中,它返回一个整数对象(的确,即使整数也是Python堆上的对象); 如果你有一个C函数返回无用参数(即函数返回void),相应的Python函数必须返回None 。你应习惯这么做(Py_RETURN_NONE宏实现):

 

    Py_INCREF(Py_None);    return Py_None;

Py_NonePython空对象的C名称。这是一个典型的Python对象而不只是一个NULL 指针,正如我们已经看到的,在大多数情况下,Py_None表示错误。

浏览次数:   【 打 印 】【 关 闭
上一篇:[转帖]Python 性能剖分工具
下一篇:《扩展和嵌入python解释器》2
 论坛登陆
用户名:
密  码:
验证码: 
Cookie 选项:
正常登陆 隐身登陆
   
没有注册?
 文章搜索
 推荐文章
 酷站推荐
 热门文章
 网站统计
关于我们 | 网站地图 | 联系我们 | 网站历史 | 友情链接 | TOP
Copyright© 2006 Sqlite中文社区  程序开发: mistletoe  站长: 林轩 陈文成