用C语言扩展Python的功能 - Python - 伯乐在线
Tuesday, May 24, 2016
11:10 PM
伯乐在线 > Python - 伯乐在线 > 所有文章 > 实践项目 > 用C语言扩展Python的功能
用C语言扩展Python的功能
2016/05/22 · 实践项目 · C语言
分享到: 0
原文出处: IBM - 肖文鹏
一、简介
Python是一门功能强大的高级脚本语言,它的强大不仅表现在其自身的功能上,而且还表现在其良好的可扩展性上,正因如此,Python已经开始受到越来越多人的青睐,并且被屡屡成功地应用于各类大型软件系统的开发过程中。
与其它普通脚本语言有所不同,Python程序员可以借助Python语言提供的API,使用C或者C几乎相同的执行性能。执行速度慢是几乎所有脚本语言都具有的共性,也是倍受人们指责的一个重要因素,Python则通过与C语言的有机结合巧妙地解决了这一问题,从而使脚本语言的应用范围得到了很大扩展。
在用Python开发实际软件系统时,很多时候都需要使用C/C实现,从而提供程序的执行性能。
本文主要介绍Python提供的C语言扩展接口,以及如何使用这些接口和C/C++语言来对Python进行功能性扩展,并辅以具体的实例讲述如何实现Python的功能扩展。
二、Python的C语言接口
Python是用C语言实现的一种脚本语言,本身具有优良的开放性和可扩展性,并提供了方便灵活的应用程序接口(API),从而使得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,删除对象
|
| | | —- | —- |
|
1
2
3
4
5
6
7
| 例1:refcount.py
class refcount:
# etc.
r1 = refcount() # 引用计数为1
r2 = r1 # 引用计数为2
del(r1) # 引用计数为1
del(r2) # 引用计数为0,删除对象
|
在C/C++中处理Python对象时,对引用计数进行正确的维护是一个关键问题,处理不好将很容易产生内存泄漏。Python的C语言接口提供了一些宏来对引用计数进行维护,最常见的是用PyINCREF()来增加使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 = PyBuildValue(“i”, 2003); assert(PyInt_Check(pInt)); int i = PyInt_AsLong(pInt); Py_DECREF(pInt); // build a float PyObject pFloat = PyBuildValue(“f”, 3.14f); assert(PyFloat_Check(pFloat)); float f = PyFloat_AsDouble(pFloat); Py_DECREF(pFloat); // build a string PyObject pString = PyBuildValue(“s”, “Python”); assert(PyString_Check(pString); int nLen = PyString_Size(pString); char s = PyString_AsString(pString); Py_DECREF(pString);
|
| | | —- | —- |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 例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 = PyBuildValue(“f”, 3.14f);
assert(PyFloat_Check(pFloat));
float f = PyFloat_AsDouble(pFloat);
Py_DECREF(pFloat);
// build a string
PyObject pString = PyBuildValue(“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);
|
| | | —- | —- |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 例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的列表类型:
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);
|
| | | —- | —- |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| 例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的字典类型:
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);
|
| | | —- | —- |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| 例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语言函数:
Python
例6:example.c int fact(int n) { if (n <= 1) return 1; else return n * fact(n - 1); }
|
| | | —- | —- |
|
1
2
3
4
5
6
7
8
| 例6:example.c
int fact(int n)
{
if (n <= 1)
return 1;
else
return n * fact(n - 1);
}
|
该函数的功能是计算某个给定自然数的阶乘,如果想在Python解释器中调用该函数,则应该首先将其实现为Python中的一个模块,这需要编写相应的封装接口,如下所示:
例7: wrap.c #include 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); }
|
| | | —- | —- |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| 例7: wrap.c
#include
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语言中的某个函数,首先要为其编写相应的导出函数,上述例子中的导出函数为wrapfact。在Python的C语言扩展中,所有的导出函数都具有相同的函数原型:
PyObject method(PyObject_ self, PyObject* args);
|
| | | —- | —- |
| 1 | PyObject* method(PyObject self, PyObject args); |
该函数是Python解释器和C函数进行交互的接口,带有两个参数:self和args。参数self只在C函数被实现为内联方法(built-in method)时才被用到,通常该参数的值为空(NULL)。参数args中包含了Python解释器要传递给C函数的所有参数,通常使用Python的C语言扩展接口提供的函数PyArgParseTuple()来获得这些参数值。
所有的导出函数都返回一个PyObject指针,如果对应的C函数没有真正的返回值(即返回值类型为void),则应返回一个全局的None对象(Py_None),并将其引用计数增1,如下所示:
PyObject method(PyObject _self, PyObject *args) { Py_INCREF(Py_None); return Py_None; }
|
| | | —- | —- |
|
1
2
3
4
5
| 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} };
|
| | | —- | —- |
|
1
2
3
4
5
| 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); }
|
| | | —- | —- |
|
1
2
3
4
5
| 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
|
| | | —- | —- |
|
1
2
3
4
| 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 >>>
|
| | | —- | —- |
|
1
2
3
4
5
6
7
8
| 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( http://www.python.org)网站着手了解所有关于Python的内容。
- 可以在Python网站上找到正式的Python C/API文档( http://www.python.org/doc/current/api/api.html)。
- 可以在Python网站上找到正式的编写Python扩展模块的文档( http://www.python.org/doc/current/api/api.html)。
拿高薪,还能扩大业界知名度!优秀的开发工程师看过来 ->《高薪招募讲师》
1
赞 2 收藏 评论
相关文章
- 使用 Python 的 Tkinter模块 开发 IRC 客户端
- 使用Python进行无线攻击:第一部分
- 利用 Python 练习数据挖掘
- 数据结构&算法实践—地精排序及改进
- 如何使用Python编写一个Lisp解释器
- Python yield 使用浅析
- 如何使用 50 行 Python 代码制作一个计算器
- Python 列表推导
- 使用Python语言编写简单的HTML5语法解析器
- Python中的生产者消费者问题
可能感兴趣的话题
- 你怎么看待买房问题(北上广深) · 146
- 程序员一个月没事做,每天盯着右下角的时… · 25
- 一个汉字是几个字符 · 2
- 有计划七、八月中去云南的吗? · 20
- 关于 Nginx 反向代理的一个问题 · 1
- 大家都用今日头条这款app吗? · 4
- 我老家人在用java做一个开源百度网盘搜索… · 26
- 马上就要毕业了,不知道是培训UI设计师… · 22
- 程序员都会破解qq号密码吗? · 89
- 本人18了,学业无成。自学电脑现实吗? · 8
« 利用装饰器给python的函数加上类型限制
让我们一起来构建一个模板引擎(一) »
登录后评论 新用户注册
直接登录



Python
Python小组话题 我有新话题
轨迹点数据坐标经纬度可视化的时候用…
nature 发起
零基础自学Python感觉很难,不像大…
keepcalm 发起 • 31 回复
已经有了python基础,正在学习数据分…
Michael翔 发起
求推荐 Python 爬虫进阶教程
亻可木亘月生 发起 • 3 回复
求python的代码,如何导入245兆的C…
nature 发起 • 8 回复
准备再学一门语言python,求推荐书籍
=_= 发起 • 6 回复
0 Scrapy爬虫 - 获取知乎用户数据
1 Python “黑魔法” 之 Generator Cor…
2 python-文本处理和正则表达式
3 python的一些误区
4 Python 的内置字符串方法(收藏专…
5 利用装饰器给python的函数加上类型限制
6 Python “黑魔法” 之 Meta Clas…
7 用C语言扩展Python的功能
8 六款好用的Python IDE
9 使用 exec 函数时需要注意的一些安…
Python工具资源 更多资源 »
SciPy库:Python的科学计算工具集
科学计算和数据分析
NumPy:Python科学计算的基石
Python, 科学计算和数据分析
webssh:基于tornado的web linux终端
Python
Python Prompt Toolkit:构建强大交互式命令行的 Pyt…
Python, 开发库 · 1
Pythonpy:在命令行中直接执行任何Python指令
Python, 命令行工具 · 1
最新评论
Re: Python “黑魔法” 之 Generator C…
Lua 中通过 coroutine.yield() 将协程挂起…所以脱离 yield, coro…
Re: Python “黑魔法” 之 Generator C…
初次接触 coroutine 是玩 Lua 时, 那时的感觉就是 yield, coroutine …
Re: 分布式消息系统尝试(rabbitmq, cele…
这也太夸张了,虽然没试过,但是半秒的延迟,是不是没设置好。
Re: 在Linux中使用matplotlib进行科…
matplotlib跟Linux没什么直接关系啊,人家是Python的包,要是Linux下绘图不是应…
Re: Python爬虫入门(3):Urllib…
为什么我用post版的时候不能自动登陆呢。只能显示出源代码
Re: 网络爬虫剖析,以Pyspider为例
很棒的文章,让我对pyspider和tornado有了更深入的了解!
Re: Python爬虫实战(4):抓取淘宝…
正在偷偷寻找第 2 个地方,看看MM们在不在发现一位模特,名字叫 李喵喵 芳龄 26 ,她在 上海市…
Re: Python “黑魔法” 之 Meta Cl…
Hi,请查看站内私信 :)

关于 Python 频道
Python频道分享 Python 开发技术、相关的行业动态。
快速链接
问题反馈与求助 »
Python工具资源 »
Python技术话题 »
关注我们
新浪微博:@Python开发者
RSS:订阅地址
推荐微信号


合作联系
Email:bd@Jobbole.com
QQ: 2302462408 (加好友请注明来意)
更多频道
小组 – 好的话题、有启发的回复、值得信赖的圈子
头条 – 分享和发现有价值的内容与观点
相亲 – 为IT单身男女服务的征婚传播平台
资源 – 优秀的工具资源导航
翻译 – 翻译传播优秀的外文文章
文章 – 国内外的精选文章
设计 – UI,网页,交互和用户体验
iOS – 专注iOS技术分享
安卓 – 专注Android技术分享
前端 – JavaScript, HTML5, CSS
Java – 专注Java技术分享
Python – 专注Python技术分享
© 2016 伯乐在线 首页 博客 资源 小组 相亲 反馈

已使用 Microsoft OneNote 2016 创建。








