我们将继续一步一步动手给Python写扩展,通过上一篇我们学习了如何写扩展,本篇将介绍一些高级话题,如异常,引用计数问题等。强烈建议先看上一篇,Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(爱之初体验)的基础知识。

一:扩展中的异常处理

高级语言如C++,Java等都有完善的异常控制,Python也不例外。但与C++不同的是,写C++你可以完全抛弃异常处理,但Python中基本是不可能的。记得Google的C++编码规范中明确指出,他们是不允许使用异常的,因为会打乱控制流,导致调试等复杂度增加,有兴趣的童鞋可以看看。但Python中你是避免不了的,很多内置函数和模块都大量的使用了异常。所以为了我们编写的模块更加Pythonic,异常处理免不了。

下面一段代码是在上一篇文章中的增强版,主要就是在异常处理方面。

#include <Python.h>
static PyObject *SpamError;
static PyObject *SpamIoError; 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<0){ //系统调用返回-1,失败;返回0,代表执行成功;返回其它的,也代表错误,如没有这个命令等
PyErr_SetString(SpamError,"system call failed");
return NULL;
}
else if(sts>0){
PyErr_SetString(SpamIoError,"Io failed");
return NULL;
}
return PyLong_FromLong(sts);
} static PyMethodDef SpamMethods[] = {
{"system", spam_system, METH_VARARGS,"Execute a shell command."},
{NULL, NULL, 0, NULL}
}; PyMODINIT_FUNC initspam(void)
{
PyObject *m;
m=Py_InitModule("spam", SpamMethods);
if (m==NULL)
return;
SpamError=PyErr_NewException("spam.error",NULL,NULL);
SpamIoError=PyErr_NewException("spam.IoError",NULL,NULL);
Py_INCREF(SpamError);
Py_INCREF(SpamIoError);
PyModule_AddObject(m,"error",SpamError);
PyModule_AddObject(m,"IoError",SpamIoError); }

我们写来看一下使用:

通过dir(),我们发现新增加了error和IoError,我们看看这到底是什么?

哦,原来spam.error就是一个异常类,是通过代码39行加入:PyModule_AddObject的函数原形,int PyModule_AddObject(PyObject *module, const char *name, PyObject *value),其中module就是Py_InitModule()返回的对象。代码39行:PyModule_AddObject(m,"error",SpamError),含义就是将SpamError这个类加入m这个模块中,并简记为“error”。而SpamError就是一个异常类,是通过35行的SpamError=PyErr_NewException("spam.error",NULL,NULL)创建的,PyObject* PyErr_NewException(char *name, PyObject *base, PyObject *dict)返回一个新创建的异常类,SpamError是程序开头定义的一个静态变量,到这里我们都知道了异常类如何定义了,那如何使用呢?

代码14行,当系统调用失败,通过PyErr_SetString(SpamError,"system call failed")设置异常,一般都是使用void PyErr_SetString(PyObject *type, const char *message)来设置异常,代码很简洁,下面通过实例看看如何使用。

我们调用了一个不存在的命令,很明显抛出的是IoError,额突然发现名字起的不好,应该为NotFound异常更加贴切。

这里需要注意37行的Py_INCREF(SpamError),由于异常可以由外部代码删除,这样将导致SpamError成为悬垂指针(也就是SpamError指向的地址被释放,但SpamError还是指向原来的地址),所以将SpamError的引用加1,这样异常类就不会被释放。

下面是几点扩展知识点:

1,如果要忽略函数抛出的异常,可以用PyErr_Clear()。因为异常只有传递到Python解释器时才起作用,所以在C API层次可以清除。

2,如果是申请内存函数(malloc等)失败,要设置异常,需要设置PyErr_NoMemory(),并需要将异常指示器返回,简单来说就是这样:return PyErr_NoMemory();

主要是所有的对象创建函数都是这么干的,所以我们要遵守规矩。

下面给一个demo程序,也就是上面代码的演化版,增加了异常清除,给模块增加变量,如模块版本信息,作者等。

#include <Python.h>
static PyObject *SpamError;
static PyObject *SpamIoError; void Pysystem(const char *command)
{
int sts;
sts = system(command);
if(sts<0){ //系统调用返回-1,失败;返回0,代表执行成功;返回其它的,也代表错误,如没有这个命令等
PyErr_SetString(SpamError,"system call failed");
}
else if(sts>0){
PyErr_SetString(SpamIoError,"Io failed");
}
} static PyObject *spam_system(PyObject *self, PyObject *args)
{
const char *command;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
Pysystem(command);
PyErr_Clear(); //清除异常
return PyLong_FromLong(0);
} static PyMethodDef SpamMethods[] = {
{"system", spam_system, METH_VARARGS,"Execute a shell command."},
{NULL, NULL, 0, NULL}
}; PyMODINIT_FUNC initspam(void)
{
PyObject *m;
PyObject *mversion =PyString_FromString("1.0.0");
PyObject *mauthor =PyString_FromString("skycrab");
m=Py_InitModule("spam", SpamMethods);
if (m==NULL)
return; SpamError=PyErr_NewException("spam.errorClass",NULL,NULL);
SpamIoError=PyErr_NewException("spam.IoErrorClass",NULL,NULL);
Py_INCREF(SpamError);
Py_INCREF(SpamIoError);
PyModule_AddObject(m,"error",SpamError);
PyModule_AddObject(m,"IoError",SpamIoError);
PyModule_AddObject(m,"version",mversion);
PyModule_AddObject(m,"author",mauthor); }

代码很简单,看交互效果。

从上面我们可以看出,的确没有抛出异常,而且返回值也的确是0。

二,引用计数

Python中使用了引用计数(为主)来解决垃圾回收,想了解Python如何进行垃圾回收可以看Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解。那么在C API层次如何控制引用的呢?

常用的有4个C API,

void Py_INCREF(PyObject *o)        //  增加引用,o不可以为NULL
void Py_XINCREF(PyObject *o) // 增加引用,o可以为NULL
void Py_DECREF(PyObject *o) // 减少引用,o不可以为NULL
void Py_XDECREF(PyObject *o) // 减少引用,o可以为NULL

在Python C API中,函数返回值一般都是返回引用,但分为2种引用,Borrowed reference和New reference(不知道翻译成什么比较合适)

我们先来看两个API声明:

PyObject* PyTuple_GetItem(PyObject *p, Py_ssize_t pos)
Return value: Borrowed reference. PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)
Return value: New reference.

我们平常使用较多的是New reference,也就是能完全拥有的对象。所谓完全拥有,简单点说就是引用计数已经加1。

而Borrowed reference其实只是一个指针,引用计数并没有增加,其实就是Python层次的Weakref(弱引用) ,所以使用时,你必须保证指向对象没有被释放。

我们来看下面一段代码:

void
bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0); PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0); /* BUG! */
}

上面完成功能就是从列表list中取出索引为0的对象item,然后将list[1]设置为0,最后打印出item。

应该没有问题吧?每当底气不足时,就是存在问题的时候。试想如下场景,当我设置list[1]时肯定要先释放list[1](del list[1]),如果list[1]对象定义了__del__方法,而在__del__方法中有可能del list[o],而PyList_GetItem返回的是Borrowed reference,这将导致item指针无效。

那如本避免呢,很简单,在设置之前手动增加一次引用,最后再减少一次就OK了,代码如下:

void
no_bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0); Py_INCREF(item);
PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0);
Py_DECREF(item);
}

Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(异常处理和引用计数)的更多相关文章

  1. Python之美[从菜鸟到高手]--2+2=5

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/yueguanghaidao/article/details/35644165     今天在伯乐在线 ...

  2. Python之美[从菜鸟到高手]--深刻理解原类(metaclass)

    本来想自己写这篇文章的,可当我读了这篇文章http://blog.jobbole.com/21351/,我打消了这个念头,因为肯定写的没有人家的好,说的通俗易懂,面面俱到.就厚着面皮修改下格式,测试下 ...

  3. Python之美[从菜鸟到高手]--生成器之全景分析

    yield指令,可以暂停一个函数并返回中间结果.使用该指令的函数将保存执行环境,并且在必要时恢复. 生成器比迭代器更加强大也更加复杂,需要花点功夫好好理解贯通. 看下面一段代码: def gen(): ...

  4. Python之美[从菜鸟到高手]--urlparse源码分析

    urlparse是用来解析url格式的,url格式如下:protocol :// hostname[:port] / path / [;parameters][?query]#fragment,其中; ...

  5. Python之美[从菜鸟到高手]--NotImplemented小析

    今天写代码时无意碰到NotImplemented,我一愣.难道是NotImplementedError的胞弟,所以略微研究了一下. NotImplemented故名思议.就是"未实现&quo ...

  6. Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解

    http://blog.csdn.net/yueguanghaidao/article/details/11274737

  7. Java之美[从菜鸟到高手演变]之设计模式

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  8. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  9. Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理

    Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理 JDK动态代理的实现及原理 作者:二青 邮箱:xtfggef@gmail.com     微博:http://weibo.com/xtfg ...

随机推荐

  1. .net中不能在DropDownList中选中多个项的解决方法

    页面中放有多个DropDownList,点击修改时候,需要根据值来设置两个DropDownList的选中项,当值为空时则需要选中默认值. 页面报错:不能在DropDownList中选中多个项. 直接粘 ...

  2. Windows XP密钥(共38枚)

    翱翔博客(http://hi.baidu.com/guoguo6688/home) Windows XP Professional VOL版密钥:=========================== ...

  3. 简单makefile的写法

    一个项目下的文件比较多,如果单个的输入,比较费劲,所以就需要把编译过程写进一个MakeFile文件中. 下面建立5个文件,3个cxx文件,2个hxx头文件 //filename main.cxx #i ...

  4. [置顶] C#扩展方法 扩你所需

    通过前面的学习,了解到:使用扩展方法,可以向现有类型“添加”方法.本文将使用扩展方法来对系统类型,自定义类型及接口进行方法扩展,一睹扩展方法的风采. 1.使用扩展方法来扩展系统类型 String是c# ...

  5. ios中的GCD

    前面我们说了block中提到它用于多线程,而gcd则是其用于多线程的典型.gcd其全称(Grand Central Dispatch) 那到底什么叫gcd,官方的解释如下: Grand Central ...

  6. popupwindow 模拟新浪、腾讯title弹框效果

    .jpg外部引用 原始文档 MainActivity.java外部引用 原始文档 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ...

  7. CSS换行2

    1.可以使用强制换行符号<br />换行.如果在一个文章里可以在文章需要换行的地方加入<br />即可实现自动换行-常说的小换行,与换行前没有间隔.实例如下图 换行说明图无间隔 ...

  8. !!!易控INSPEC组态软件开发小结——-一次工程文件损坏和处理经过

    从加入红橡开始熟悉和使用易控(INSPEC)组态软件,值得赞扬的是INSPEC的开放性和对C#语言的支持,除此之外,便也没有感觉它与其他组态软件有太多优势,有人说INSPEC软件授权比国内其他同类的组 ...

  9. 使用命令行将Excel数据表导入Mysql中的方法小结

    从Excel数据表导入MySQL,已经做过好几次了,但每次都会碰到各种问题:invalid utf8 character string, data too long, ...,浪费了不少时间 为了提高 ...

  10. [Jobdu] 题目1521:二叉树的镜像

    不知道怎么回事下面的代码通过了4个测试用例,还有1个测试用例始终是Runtime Error,各位帮我看一下是哪里出了问题 镜像输出两种方法,一种是递归进行调整,另外一种就是直接在先序遍历的基础上进行 ...