快速实现python c扩展模块
1 python扩展模块的组成
在python中,对于一些和系统相关的模块或者对性能要求很高的模块,通常会把这个模块C化。扩展模块中主要包含下面几个部分:
- init函数,函数名为:init+模块名,这个函数负责初始化模块,包括设置模块中的方法、对象和其它相关数据的初始化。这个函数是必须的,在脚本中第一次导入这个模块的时候,会先执行这个方法。
- 定义模块方法描述表,它是一个static类型的PyMethodDef数据结构,用来描述模块中定义的方法。
- C函数定义,这些函数是方法描述表中方法的具体实现。
- 如果模块中定义了类,那么还要定义类方法描述表和对象类型。
2 实现python扩展模块实例
当然,有了上面的组成部分,你还是不知道怎么实现一个模块,下面就用官方的一个例子来演示怎么实现一个python扩展模块,这个扩展模块用来实现在python中执行命令行命令。
// spam.c
1 #include "Python.h" static PyObject *SpamError; static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts; if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts < ) {
PyErr_SetString(SpamError, "System command failed");
return NULL;
}
return PyLong_FromLong(sts);
} static PyMethodDef SpamMethods[] = {
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
{NULL, NULL, , NULL} /* Sentinel */
}; PyMODINIT_FUNC
initspam(void)
{
PyObject *m; m = Py_InitModule("spam", SpamMethods);
if (m == NULL)
return; SpamError = PyErr_NewException("spam.error", NULL, NULL);
Py_INCREF(SpamError);
PyModule_AddObject(m, "error", SpamError);
}
上面的initspam是模块的初始化函数,函数开始调用了Py_InitModule初始化了一个名为spam的模块,模块的方法描述表是SpamMethods,它描述了模块有个名为system的方法,这个方法的c/c++实现是spam_system函数。从spam_system函数可以看到它就是调用system函数执行从python传过来的命令。有了上面的代码,我们怎样在python中使用了?很简单,先将上面代码编译成动态链接库,然后直接在python中用import语句导入这个模块就可以用了。在Windows下的用vs编译就行,不过在vs建立了dll工程后,需要设置下工程的属性,目的是设置python扩展涉及到的头文件路径和动态库。具体设置如下:先在VC++目录中设置include和lib路径,然后在链接器的附加依赖项中添加python27.lib库。
设置好后直接编译就可以了,将编译生成的dll文件后缀名改成pyd,然后就可以在python中直接用import导入这个模块了。是不是非常的简单!!!!
3 实现python扩展模块中定义类
上面的实现是在模块中定义函数来实现执行命令行命令,我们也可以在模块中定义类,然后用类的方法来执行这个命令。代码如下:
// spam.c
1 #include "Python.h" static PyObject *SpamError; static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts; if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts < ) {
PyErr_SetString(SpamError, "System command failed");
return NULL;
}
return PyLong_FromLong(sts);
} static PyMethodDef SpamMethods[] = {
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
{NULL, NULL, , NULL} /* Sentinel */
}; PyTypeObject *SpamType = NULL; PyMODINIT_FUNC
initspam(void)
{
static PyTypeObject _SpamType = {
PyObject_HEAD_INIT(NULL)
, // ob_size
"spam.Spam", // tp_name
sizeof(PyObject), // tp_basicsize
, // tp_itemsize
, // tp_dealloc
, // tp_print
, // tp_getattr
, // tp_setattr
, // tp_compare
, // tp_repr
, // tp_as_number
, // tp_as_sequence
, // tp_as_mapping
, // tp_hash
, // tp_call
, // tp_str
, // tp_getattro
, // tp_setattro
, // tp_as_buffer
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE , // tp_flags
, // tp_doc
, // tp_traverse
, // tp_clear
, // tp_richcompare
, // tp_weaklistoffset
, // tp_iter
, // tp_iternext
SpamMethods, // tp_methods
, // tp_members
, // tp_getset
, // tp_base
, // tp_dict
, // tp_descr_get
, // tp_descr_set
, // tp_dictoffset
, // tp_init
, // tp_alloc
PyType_GenericNew, // tp_new
}; PyObject *m; m = Py_InitModule("spam", NULL);
if (m == NULL)
return;
if (PyType_Ready(&_SpamType) < )
return;
SpamType = &_SpamType;
Py_INCREF(SpamType);
PyModule_AddObject(m, "Spam", (PyObject*)SpamType);
SpamError = PyErr_NewException("spam.error", NULL, NULL);
Py_INCREF(SpamError);
PyModule_AddObject(m, "error", SpamError);
}
上面的代码与之前的代码只是多了个Spam类的定义,使用的时候通过Spam的实例化对象来调用system函数。
4 Python/C API涉及的引用计数问题
通过上面的例子,是不是觉得写python的C扩展模块非常的简单呢?其实不然,主要是python中有个引用计数问题,在写扩展模块的时候必须非常小心的处理,否则很有容易导致内存泄露。根据python官方的定义,在Python/C API中,引用计数的行为被归纳为三种:new reference、borrow reference和steal reference,前两种用于描述返回PyObject*类型的函数对返回的这个对象的引用计数的行为;后一种用于将一个PyObject*类型传入函数后,函数对这个对象的引用计数的行为。new referenc表示函数将这个对象引用的所有权转交给函数调用者了,由函数的调用者来管理这个引进的计数,也就是说调用者不用这个引用的时候必须显示的调用 Py_DECREF()或者
Py_XDECREF()来释放这个引用,典型的函数是
PyObject_、
PyNumber_
、PySequence_和
PyMapping_
;borrow reference与new reference刚好相反,表示函数的调用者只管用这个引用,不用关心它的引用计数,用完了也不用显示调用Py_DECREF()或者
Py_XDECREF()来释放这个引用,典型的函数是PyList_GetItem、PyTuple_GetItem
;steal reference表示函数内部只会使用这个引用,不会调用Py_INCREF来增加这个引用的引用计数,相当于“偷了”被调用者的一个引用计数,典型的函数是PyList_SetItem()和
。因此,在编写C扩展的时,如果遇到某个Python/C API不确定是哪种reference的时候,建议查下官方文档,文档中会明确的说明这个函数是哪类reference(如下图所示),这样能大大减少引用计数的问题。PyTuple_SetItem()
5 参考
快速实现python c扩展模块的更多相关文章
- java程序员快速掌握python系列——概述
这一系列主要是总结学习python过程中的方方面面(已经学完,时间大概是一周左右).当然限于个人水平java也就是够用,python短时间内也不可能深入到哪里去.所以这次的分享的目的是能够快速使用py ...
- 快速查询Python脚本语法
/********************************************************************* * 快速查询Python脚本语法 * 说明: * Char ...
- 程序员带你十天快速入门Python,玩转电脑软件开发(四)
本系列文章立志于从一个已经习得一门编程语言的基础之上,全面介绍Python的相关开发过程和相关经验总结.本篇文章主要是基于上一篇的程序员带你十天快速入门Python,玩转电脑软件开发(三)的基础之上, ...
- 程序员带你十天快速入门Python,玩转电脑软件开发(三)
声明:本次教程主要适用于已经习得一门编程语言的程序员.想要学习第二门语言.有梦想,立志做全栈攻城狮的你 . 如果是小白,也可以学习本教程.不过可能有些困难.如有问题在文章下方进行讨论.或者添加QQ群5 ...
- 程序员带你十天快速入门Python,玩转电脑软件开发(二)
关注今日头条-做全栈攻城狮,学代码也要读书,爱全栈,更爱生活.提供程序员技术及生活指导干货. 如果你真想学习,请评论学过的每篇文章,记录学习的痕迹. 请把所有教程文章中所提及的代码,最少敲写三遍,达到 ...
- 程序员带你十天快速入门Python,玩转电脑软件开发(一)
关注今日头条-做全栈攻城狮,学代码也要读书,爱全栈,更爱生活.提供程序员技术及生活指导干货. 如果你真想学习,请评论学过的每篇文章,记录学习的痕迹. 请把所有教程文章中所提及的代码,最少敲写三遍,达到 ...
- 新手如何快速入门Python
学习任何一门语言都是从入门(1年左右),通过不间断练习达到熟练水准(3到5年),少数人最终能精通语言,成为执牛耳者,他们是金字塔的最顶层.虽然万事开头难,但好的开始是成功的一半,今天这篇文章就来谈谈如 ...
- 快速开始Python/WSGI应用程序
快速开始Python-wsig应用程序 官方参考文档 安装 uwsgi 安装 pip install uwsgi uwsgi --version # 查看 uwsgi 版本 测试 uwsgi 是否正常 ...
- python从入门到精通之30天快速学python视频教程
点击了解更多Python课程>>> python从入门到精通之30天快速学python视频教程 课程目录: python入门教程-1-Python编程语言历史及特性.mkv pyth ...
随机推荐
- (译)Windsor入门教程---第三部分 编写第一个Installer
原文:http://docs.castleproject.org/Windsor.Windsor-tutorial-ASP-NET-MVC-3-application-To-be-Seen.ashx ...
- (简单) POJ 3984 迷宫问题,BFS。
Description 定义一个二维数组: int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, ...
- objective-c学习笔记2
Objective-c学习笔记 1.cocoa的对象初始化一般使用alloc和init两个方法,不适用new,其中alloc用于分配内存,init用于初始化,因为初始化方法返回的对象可能和分配的对象不 ...
- 2、手把手教你Extjs5(二)项目中文件的加载过程
上一节中用sencha工具自动创建了一个项目,并且可以在浏览器中查看.现在我们来看看js类加载过程.如下图所示: 1、首先:浏览器中输入 localhost:1841 ,调用 index.html; ...
- 《算法导论》Problem 2-4 Inversions
在Merge Sort的基础上改改就好了. public class Inversions { public static int inversions(int [] A,int p, int r) ...
- jQuery学习笔记(二)
1. 基本选择器 #id ("#one") .class $(".two") element (根据给定的元素名匹配元素) $("div& ...
- Android线程之Thread 、Runnable 的两个例子
Thread例子: package fy.test; import android.app.Activity; import android.os.Bundle; import android.os. ...
- BZOJ 1653 [Usaco2006 Feb]Backward Digit Sums ——搜索
[题目分析] 劳逸结合好了. 杨辉三角+暴搜. [代码] #include <cstdio> #include <cstring> #include <cmath> ...
- cocos2d-x 跨平台usleep方法
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) #define usleep(t) Sleep(t) #else #include <unistd.h ...
- mysql server的安装和配置
YSQL-5.7.9.1解压版 例如我的在D:\Program Files\MySQL\MySQL Server 5.7(解压时名字mysql-installer-community-5.7.9.1可 ...