使用C语言扩展Python程序的简单入门指引


Posted in Python onApril 14, 2015

一、简介

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编写的脚本在运行时的速度,从而满足实际需要。

Python 相关文章推荐
Python模块学习 datetime介绍
Aug 27 Python
python备份文件以及mysql数据库的脚本代码
Jun 10 Python
pytyon 带有重复的全排列
Aug 13 Python
python小技巧之批量抓取美女图片
Jun 06 Python
举例讲解Python设计模式编程的代理模式与抽象工厂模式
Jan 16 Python
python下os模块强大的重命名方法renames详解
Mar 07 Python
python验证码识别教程之利用滴水算法分割图片
Jun 05 Python
快速解决pandas.read_csv()乱码的问题
Jun 15 Python
Django中使用Celery的教程详解
Aug 24 Python
Python-接口开发入门解析
Aug 01 Python
Flask教程之重定向与错误处理实例分析
Aug 01 Python
基于Python生成个性二维码过程详解
Mar 05 Python
在Python中封装GObject模块进行图形化程序编程的教程
Apr 14 #Python
用PyQt进行Python图形界面的程序的开发的入门指引
Apr 14 #Python
使用C语言来扩展Python程序和Zope服务器的教程
Apr 14 #Python
用Python中的wxPython实现最基本的浏览器功能
Apr 14 #Python
Python中SOAP项目的介绍及其在web开发中的应用
Apr 14 #Python
Python中的XML库4Suite Server的介绍
Apr 14 #Python
Python pickle模块用法实例
Apr 14 #Python
You might like
如何对PHP程序中的常见漏洞进行攻击(下)
2006/10/09 PHP
Smarty模板学习笔记之Smarty简介
2014/05/20 PHP
PHP的微信支付接口使用方法讲解
2019/03/08 PHP
把JS与CSS写在同一个文件里的书写方法
2007/06/02 Javascript
javascript之通用简单的table选项卡实现(二)
2010/05/09 Javascript
JavaScript 更严格的相等 [译]
2012/09/20 Javascript
JS随机生成不重复数据的实例方法
2013/07/17 Javascript
js操作模态窗口及父子窗口间相互传值示例
2014/06/09 Javascript
javascript中的3种继承实现方法
2016/01/27 Javascript
JavaScript常用函数工具集:lao-utils
2016/03/01 Javascript
vue.js+boostrap项目实践(案例详解)
2016/09/21 Javascript
详解Angular的数据显示优化处理
2016/12/26 Javascript
js 颜色选择插件
2017/01/23 Javascript
addEventListener()与removeEventListener()解析
2017/04/20 Javascript
详谈Node.js之操作文件系统
2017/08/29 Javascript
nodejs对express中next函数的一些理解
2017/09/08 NodeJs
vue微信分享 vue实现当前页面分享其他页面
2017/12/02 Javascript
如何使用pm2快速将项目部署到远程服务器
2019/03/12 Javascript
JS查找孩子节点简单示例
2019/07/25 Javascript
thinkjs微信中控之微信鉴权登陆的实现代码
2019/08/08 Javascript
原生JavaScript之es6中Class的用法分析
2020/02/23 Javascript
我所理解的JavaScript中的this指向
2020/09/04 Javascript
Pycharm取消py脚本中SQL识别的方法
2018/11/29 Python
django2笔记之路由path语法的实现
2019/07/17 Python
使用python的turtle绘画滑稽脸实例
2019/11/21 Python
利用python+request通过接口实现人员通行记录上传功能
2021/01/13 Python
美国领先的奢侈手表在线零售商:WatchMaxx
2017/12/17 全球购物
美国领先的眼镜和太阳镜在线零售商:Glasses.com
2019/08/26 全球购物
澳大利亚著名的纺织品品牌:Canningvale
2020/05/05 全球购物
依法行政工作汇报
2014/10/28 职场文书
幼儿园新生开学寄语
2015/05/27 职场文书
圣贤教育改变命运观后感
2015/06/16 职场文书
导游词之晋城蟒河
2019/12/12 职场文书
MySQL数字类型自增的坑
2021/05/07 MySQL
Python实现PIL图像处理库绘制国际象棋棋盘
2021/07/16 Python
Java spring定时任务详解
2021/10/05 Java/Android