| 网站首页 | 测试咨讯 | 新手入门 | 专注性能 | 测试技术 | 测试管理 | 测试工具 | 开发社区 | 工具下载 | 资料下载 | 测试论坛 | 

[CNTester联盟群]交流群:34446273/21968356/64461572 白盒群:18400216 自动化群:2706508 性能群:4498858 外包群:59649884 管理群(需有管理经验):64442523

新手入门
 入门指南
 经验之谈
测试咨讯
 行业新闻
 网站动态
 人才就业
 CNTester基金
 标准规范
专注性能
 性能测试知识
 性能测试工具
 程序设计性能
 数据库性能
 Web应用服务器性能
 操作系统性能
 服务器与网络性能
测试技术
 用例设计
 性能测试
 自动化测试
 Web测试
 面向对象测试
 综合技术
 阶段性测试
 行业类测试
测试管理
 项目管理
 项目案例
 质量管理
 软件过程
测试工具
 Mercury系列
 Rational系列
 测试管理工具
 性能测试工具
 功能测试工具
 单元测试工具
 其它测试工具
开发社区
 Java技术
 DotNet技术
 C技术
 数据库技术
 操作系统
 服务器与中间件
工具下载
 测试工具
 开发工具
 企业工程
 网络通信
 媒体桌面
 游戏娱乐
 其他工具
资料下载
 测试资料
 程序设计
 数据库
 操作系统
 应用服务器
 电子期刊
 其他资料
 
 
您现在的位置: 中国测试员网站 >> 开发社区 >> C技术 >> 文章正文
  [图文]用C语言扩展Python的功能           ★★★ 【字体:
如何找到、使用和参与开放源代码项目
作者:肖文鹏    文章来源:IBM    点击数:    更新时间:2007-7-1    
Pyton和C分别有着各自的优缺点,用Python开发程序速度快,可靠性高,并且有许多现成模块可供使用,但执行速度相对较慢;C语言则正好相反,其执行速度快,但开发效率低。为了充分利用两种语言各自的优点,比较好的做法是用Python开发整个软件框架,而用C语言实现其关键模块。本文介绍如何利用C语言来扩展Python的功能,并辅以具体的实例讲述如何编写Python的扩展模块。

一、简介

Python是一门功能强大的高级脚本语言,它的强大不仅表现在其自身的功能上,而且还表现在其良好的可扩展性上,正因如此,Python已经开始受到越来越多人的青睐,并且被屡屡成功地应用于各类大型软件系统的开发过程中。

与其它普通脚本语言有所不同,Python程序员可以借助Python语言提供的API,使用C或者C++来对Python进行功能性扩展,从而即可以利用Python方便灵活的语法和功能,又可以获得与C或者C++几乎相同的执行性能。执行速度慢是几乎所有脚本语言都具有的共性,也是倍受人们指责的一个重要因素,Python则通过与C语言的有机结合巧妙地解决了这一问题,从而使脚本语言的应用范围得到了很大扩展。

在用Python开发实际软件系统时,很多时候都需要使用C/C++来对Python进行扩展。最常见的情况是目前已经存在一个用C编写的库,需要在Python语言中使用该库的某些功能,此时就可以借助Python提供的扩展功能来实现。此外,由于Python从本质上讲还是一种脚本语言,某些功能用Python实现可能很难满足实际软件系统对执行效率的要求,此时也可以借助Python提供的扩展功能,将这些关键代码段用C或者C++实现,从而提供程序的执行性能。

本文主要介绍Python提供的C语言扩展接口,以及如何使用这些接口和C/C++语言来对Python进行功能性扩展,并辅以具体的实例讲述如何实现Python的功能扩展。




二、Python的C语言接口

Python是用C语言实现的一种脚本语言,本身具有优良的开放性和可扩展性,并提供了方便灵活的应用程序接口(API),从而使得C/C++程序员能够在各个级别上对Python解释器的功能进行扩展。在使用C/C++对Python进行功能扩展之前,必须首先掌握Python解释所提供的C语言接口。

2.1 Python对象(PyObject)

Python是一门面向对象的脚本语言,所有的对象在Python解释器中都被表示成PyObject,PyObject结构包含Python对象的所有成员指针,并且对Python对象的类型信息和引用计数进行维护。在进行Python的扩展编程时,一旦要在C或者C++中对Python对象进行处理,就意味着要维护一个PyObject结构。

在Python的C语言扩展接口中,大部分函数都有一个或者多个参数为PyObject指针类型,并且返回值也大都为PyObject指针。

2.2 引用计数

为了简化内存管理,Python通过引用计数机制实现了自动的垃圾回收功能,Python中的每个对象都有一个引用计数,用来计数该对象在不同场所分别被引用了多少次。每当引用一次Python对象,相应的引用计数就增1,每当消毁一次Python对象,则相应的引用就减1,只有当引用计数为零时,才真正从内存中删除Python对象。

下面的例子说明了Python解释器如何利用引用计数来对Pyhon对象进行管理:

例1:refcount.py
class refcount:
    # etc.
r1 = refcount() # 引用计数为1
r2 = r1         # 引用计数为2
del(r1)         # 引用计数为1
del(r2)         # 引用计数为0,删除对象

在C/C++中处理Python对象时,对引用计数进行正确的维护是一个关键问题,处理不好将很容易产生内存泄漏。Python的C语言接口提供了一些宏来对引用计数进行维护,最常见的是用Py_INCREF()来增加使Python对象的引用计数增1,用Py_DECREF()来使Python对象的引用计数减1。

2.3 数据类型

Python定义了六种数据类型:整型、浮点型、字符串、元组、列表和字典,在使用C语言对Python进行功能扩展时,首先要了解如何在C和Python的数据类型间进行转化。

2.3.1 整型、浮点型和字符串

在Python的C语言扩展中要用到整型、浮点型和字符串这三种数据类型时相对比较简单,只需要知道如何生成和维护它们就可以了。下面的例子给出了如何在C语言中使用Python的这三种数据类型:

例2:typeifs.c
// build an integer
PyObject* pInt = Py_BuildValue("i", 2003);
assert(PyInt_Check(pInt));
int i = PyInt_AsLong(pInt);
Py_DECREF(pInt);
// build a float
PyObject* pFloat = Py_BuildValue("f", 3.14f);
assert(PyFloat_Check(pFloat));
float f = PyFloat_AsDouble(pFloat);
Py_DECREF(pFloat);
// build a string
PyObject* pString = Py_BuildValue("s", "Python");
assert(PyString_Check(pString);
int nLen = PyString_Size(pString);
char* s = PyString_AsString(pString);
Py_DECREF(pString);

2.3.2 元组

Python语言中的元组是一个长度固定的数组,当Python解释器调用C语言扩展中的方法时,所有非关键字(non-keyword)参数都以元组方式进行传递。下面的例子示范了如何在C语言中使用Python的元组类型:

例3:typetuple.c
// create the tuple
PyObject* pTuple = PyTuple_New(3);
assert(PyTuple_Check(pTuple));
assert(PyTuple_Size(pTuple) == 3);
// set the item
PyTuple_SetItem(pTuple, 0, Py_BuildValue("i", 2003));
PyTuple_SetItem(pTuple, 1, Py_BuildValue("f", 3.14f));
PyTuple_SetItem(pTuple, 2, Py_BuildValue("s", "Python"));
// parse tuple items
int i;
float f;
char *s;
if (!PyArg_ParseTuple(pTuple, "ifs", &i, &f, &s))
    PyErr_SetString(PyExc_TypeError, "invalid parameter");
// cleanup
Py_DECREF(pTuple);

2.3.3 列表

Python语言中的列表是一个长度可变的数组,列表比元组更为灵活,使用列表可以对其存储的Python对象进行随机访问。下面的例子示范了如何在C语言中使用Python的列表类型:

例4:typelist.c
// create the list
PyObject* pList = PyList_New(3); // new reference
assert(PyList_Check(pList));
// set some initial values
for(int i = 0; i < 3; ++i)
    PyList_SetItem(pList, i, Py_BuildValue("i", i));
// insert an item
PyList_Insert(pList, 2, Py_BuildValue("s", "inserted"));
// append an item
PyList_Append(pList, Py_BuildValue("s", "appended"));
// sort the list
PyList_Sort(pList);
// reverse the list
PyList_Reverse(pList);
// fetch and manipulate a list slice
PyObject* pSlice = PyList_GetSlice(pList, 2, 4); // new reference
for(int j = 0; j < PyList_Size(pSlice); ++j) {
  PyObject *pValue = PyList_GetItem(pList, j);
  assert(pValue);
}
Py_DECREF(pSlice);
// cleanup
Py_DECREF(pList);

2.3.4 字典

Python语言中的字典是一个根据关键字进行访问的数据类型。下面的例子示范了如何在C语言中使用Python的字典类型:

例5:typedic.c
// create the dictionary
PyObject* pDict = PyDict_New(); // new reference
assert(PyDict_Check(pDict));
// add a few named values
PyDict_SetItemString(pDict, "first", 
                     Py_BuildValue("i", 2003));
PyDict_SetItemString(pDict, "second", 
                     Py_BuildValue("f", 3.14f));
// enumerate all named values
PyObject* pKeys = PyDict_Keys(); // new reference
for(int i = 0; i < PyList_Size(pKeys); ++i) {
  PyObject *pKey = PyList_GetItem(pKeys, i);
  PyObject *pValue = PyDict_GetItem(pDict, pKey);
  assert(pValue);
}
Py_DECREF(pKeys);
// remove a named value
PyDict_DelItemString(pDict, "second");
// cleanup
Py_DECREF(pDict);




三、Python的C语言扩展

3.1 模块封装

在了解了Python的C语言接口后,就可以利用Python解释器提供的这些接口来编写Python的C语言扩展,假设有如下一个C语言函数:

例6:example.c
int fact(int n)
{
  if (n <= 1) 
    return 1;
  else 
    return n * fact(n - 1);
} 

该函数的功能是计算某个给定自然数的阶乘,如果想在Python解释器中调用该函数,则应该首先将其实现为Python中的一个模块,这需要编写相应的封装接口,如下所示:

例7: wrap.c
#include <Python.h>
PyObject* wrap_fact(PyObject* self, PyObject* args) 
{
  int n, result;
  
  if (! PyArg_ParseTuple(args, "i:fact", &n))
    return NULL;
  result = fact(n);
  return Py_BuildValue("i", result);
}
static PyMethodDef exampleMethods[] = 
{
  {"fact", wrap_fact, METH_VARARGS, "Caculate N!"},
  {NULL, NULL}
};
void initexample() 
{
  PyObject* m;
  m = Py_InitModule("example", exampleMethods);
}

一个典型的Python扩展模块至少应该包含三个部分:导出函数、方法列表和初始化函数。

3.2 导出函数

要在Python解释器中使用C语言中的某个函数,首先要为其编写相应的导出函数,上述例子中的导出函数为wrap_fact。在Python的C语言扩展中,所有的导出函数都具有相同的函数原型:

PyObject* method(PyObject* self, PyObject* args);

该函数是Python解释器和C函数进行交互的接口,带有两个参数:self和args。参数self只在C函数被实现为内联方法(built-in method)时才被用到,通常该参数的值为空(NULL)。参数args中包含了Python解释器要传递给C函数的所有参数,通常使用Python的C语言扩展接口提供的函数PyArg_ParseTuple()来获得这些参数值。

所有的导出函数都返回一个PyObject指针,如果对应的C函数没有真正的返回值(即返回值类型为void),则应返回一个全局的None对象(Py_None),并将其引用计数增1,如下所示:

PyObject* method(PyObject *self, PyObject *args) 
{
  Py_INCREF(Py_None);
  return Py_None;
}

3.3 方法列表

方法列表中给出了所有可以被Python解释器使用的方法,上述例子对应的方法列表为:

static PyMethodDef exampleMethods[] = 
{
  {"fact", wrap_fact, METH_VARARGS, "Caculate N!"},
  {NULL, NULL}
};

方法列表中的每项由四个部分组成:方法名、导出函数、参数传递方式和方法描述。方法名是从Python解释器中调用该方法时所使用的名字。参数传递方式则规定了Python向C函数传递参数的具体形式,可选的两种方式是METH_VARARGS和METH_KEYWORDS,其中METH_VARARGS是参数传递的标准形式,它通过Python的元组在Python解释器和C函数之间传递参数,若采用METH_KEYWORD方式,则Python解释器和C函数之间将通过Python的字典类型在两者之间进行参数传递。

3.4 初始化函数

所有的Python扩展模块都必须要有一个初始化函数,以便Python解释器能够对模块进行正确的初始化。Python解释器规定所有的初始化函数的函数名都必须以init开头,并加上模块的名字。对于模块example来说,则相应的初始化函数为:

void initexample() 
{
  PyObject* m;
  m = Py_InitModule("example", exampleMethods);
}

当Python解释器需要导入该模块时,将根据该模块的名称查找相应的初始化函数,一旦找到则调用该函数进行相应的初始化工作,初始化函数则通过调用Python的C语言扩展接口所提供的函数Py_InitModule(),来向Python解释器注册该模块中所有可以用到的方法。

3.5 编译链接

要在Python解释器中使用C语言编写的扩展模块,必须将其编译成动态链接库的形式。下面以RedHat Linux 8.0为例,介绍如何将C编写的Python扩展模块编译成动态链接库:

[xiaowp@gary code]$ gcc -fpic -c -I/usr/include/python2.2 \
                    -I /usr/lib/python2.2/config \
                    example.c wrapper.c
[xiaowp@gary code]$ gcc -shared -o example.so example.o wrapper.o

3.6 引入Python解释器

当生成Python扩展模块的动态链接库后,就可以在Python解释器中使用该扩展模块了,与Python自带的模块一样,扩展模块也是通过import命令引入后再使用的,如下所示:

[xiaowp@gary code]$ python
Python 2.2.1 (#1, Aug 30 2002, 12:15:30)
[GCC 3.2 20020822 (Red Hat Linux Rawhide 3.2-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.fact(4)
24
>>>



四、结束语

作为一门功能强大的脚本语言,Python将被更加广泛地应用于各个领域。为了克服脚本语言执行速度慢的问题,Python提供了相应的C语言扩展接口,通过将影响执行性能的关键代码用C语言实现,可以很大程度上提高用Python编写的脚本在运行时的速度,从而满足实际需要。



参考资料

  1. 可以从Python( http://www.python.org)网站着手了解所有关于Python的内容。
  2. 可以在Python网站上找到正式的Python C/API文档( http://www.python.org/doc/current/api/api.html)。
  3. 可以在Python网站上找到正式的编写Python扩展模块的文档( http://www.python.org/doc/current/api/api.html)。


关于作者

肖文鹏,是北京理工大学计算机系的一名硕士研究生,主要从事操作系统和分布式计算环境的研究,喜爱Linux和Python。你可以通过 xiaowp@263.net与他取得联系。


文章录入:root    责任编辑:root 
  • 上一个文章:

  • 下一个文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
     
    最新热点 最新推荐 相关文章
    · 面向对象设计与编程核心技术
    · Tomcat系统安全管理与权限管理
    · 雅虎公司C#笔试题
    · 利用Java事件处理机制实现录制、回放功能
    · apache+tomcat+mysql负载均衡和集群
    · Linux 指令大全
    · 应用服务器内存泄露问题诊断一例
    · 追求代码质量: 软件架构的代码质量
    · 对话 UNIX
    · 关于一些算法问题与朋友的讨论
    · 对话 UNIX
    · nmon 性能:分析 AIX 和 Linux 性能的免费
    · 追求代码质量: 谨防紧密耦合!
    · 应用服务器内存泄露问题诊断一例
    · Linux 指令大全
    · C 编程最佳实践
    · LPI 证书 101 考试准备,第2部分 [正则表
    · LPI 证书 101 考试准备,第1 部分 [使用L
    · Linux 究竟是什么?
    · 利用Java事件处理机制实现录制、回放功能
    内存管理内幕
    安全编程: 防止缓冲区溢出
    安全编程: 安全地调用组件
    内存调试技巧
    C 语言中的指针和内存泄漏
    C 编程最佳实践
     
     
     
    ======> [CNTester联盟群]交流群:34446273/21968356/64461572 白盒群:18400216 自动化群:2706508 性能群:4498858 外包群:59649884 管理群(需有管理经验):64442523
    | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告 | 管理登录 | 

    Copyright@2007 by CNTester.com 中国测试员网站 桂ICP备07005590

    本站为开源免费网站,非商业赢利性组织。本站文章部分从网络搜索获取,如果您认为某些侵犯了您的权益,麻烦您联系本站,我们会尽快删除相关内容,同时也希望您的谅解,我们的初衷是为了让更多人去学习这方面的知识,让行业有更好的发展。

    联系电话: 15021358905