在上一节中,讨论了在用C语言扩展Python模块时,应该如何处理无处不在的引用计数问题。重点关注的是在实现一个C Python的函数时,对于一个PyObject对象,何时调用Py_INCREF和Py_DECREF。在编写C语言代码时,需要了解Python提供的C/C++ API的实现细节,特别是有的API内部实现会调用Py_INCREF,这时自己编写的函数可能需要调用Py_DECREF,而有的API内部实现只是borrowed reference,此时一般不应该调用Py_DECREF。

本节讨论在C扩展Python时,如何对异常和错误进行处理。Python解释器的实现中有一个重要的约定:当一个函数失败,它应该设置一个exception condition并返回一个错误值(通常是NULl指针)。异常是存放在解释器的一个全局变量中,如果这个变量是NULL,那么没有异常发生。另外一个全局变量存放的是跟异常相关的值,还有一个变量包含了stack traceback,记录了产生错误时的Python Code。这三个变量对应Python的sys模块的三个变量,sys.exc_type, sys.exc_value, sys.exc_traceback。在1.5版本之后,这三个变量用exc_info()代替了。

这三个全局的变量在C Python 源码中是存放在PyThreadState *_PyThreadState_Current这个结构体中的, 而_PyThreadState_Current是在pythonrun.c的Py_InitializeEx中初始化的。

PyThreadState结构体定义:

红色框中的三个变量就是error indicator。

常见的Python设置异常的接口

Python API定义了一系列的function来设置不同类型的异常,定义在Python源码中的Python/error.c中。

PyErr_SetString:

最常用的莫过于PyErr_SetString,函数的原型为:

函数的作用是设置Python解释器的全局error indicator。

参数分别为一个exception对象和一个描述异常的字符串。exception object通常是一个已经定义好的object, 不需要增加它的引用计数,在PyErr_SetString源码中已经调用了Py_XINCREF(exception)。

/* Predefined exceptions */

PyAPI_DATA(PyObject *) PyExc_BaseException;
PyAPI_DATA(PyObject *) PyExc_Exception;
PyAPI_DATA(PyObject *) PyExc_StopIteration;
PyAPI_DATA(PyObject *) PyExc_GeneratorExit;
PyAPI_DATA(PyObject *) PyExc_StandardError;
PyAPI_DATA(PyObject *) PyExc_ArithmeticError;
PyAPI_DATA(PyObject *) PyExc_LookupError; PyAPI_DATA(PyObject *) PyExc_AssertionError;
PyAPI_DATA(PyObject *) PyExc_AttributeError;
PyAPI_DATA(PyObject *) PyExc_EOFError;
PyAPI_DATA(PyObject *) PyExc_FloatingPointError;
PyAPI_DATA(PyObject *) PyExc_EnvironmentError;
PyAPI_DATA(PyObject *) PyExc_IOError;
PyAPI_DATA(PyObject *) PyExc_OSError;
PyAPI_DATA(PyObject *) PyExc_ImportError;
PyAPI_DATA(PyObject *) PyExc_IndexError;
PyAPI_DATA(PyObject *) PyExc_KeyError;
PyAPI_DATA(PyObject *) PyExc_KeyboardInterrupt;
PyAPI_DATA(PyObject *) PyExc_MemoryError;
PyAPI_DATA(PyObject *) PyExc_NameError;
PyAPI_DATA(PyObject *) PyExc_OverflowError;
PyAPI_DATA(PyObject *) PyExc_RuntimeError;
PyAPI_DATA(PyObject *) PyExc_NotImplementedError;
PyAPI_DATA(PyObject *) PyExc_SyntaxError;
PyAPI_DATA(PyObject *) PyExc_IndentationError;
PyAPI_DATA(PyObject *) PyExc_TabError;
PyAPI_DATA(PyObject *) PyExc_ReferenceError;
PyAPI_DATA(PyObject *) PyExc_SystemError;
PyAPI_DATA(PyObject *) PyExc_SystemExit;
PyAPI_DATA(PyObject *) PyExc_TypeError;
PyAPI_DATA(PyObject *) PyExc_UnboundLocalError;
PyAPI_DATA(PyObject *) PyExc_UnicodeError;
PyAPI_DATA(PyObject *) PyExc_UnicodeEncodeError;
PyAPI_DATA(PyObject *) PyExc_UnicodeDecodeError;
PyAPI_DATA(PyObject *) PyExc_UnicodeTranslateError;
PyAPI_DATA(PyObject *) PyExc_ValueError;
PyAPI_DATA(PyObject *) PyExc_ZeroDivisionError;

PyErr_SetObject:

从PyErr_SetString实现的源码中可以看到,内部调用了PyErr_SetObject, PyErr_SetObject内部又调用了PyErr_Restore。

PyErr_SetObject函数原型是:

PyObject* PyErr_Occurred():

函数返回当前是否有异常发生,如果有返回current exception object【borrowed reference】,否则返回NULL

int PyErr_ExceptionMatches(PyObject* exc)

int PyErr_GivenExceptionMatches(PyObject* given, PyObject* exc):

判断给定的异常对象是否符合exc类型。

PyErr_Clear():

清除Python解释器的error indicator。Python解释器不会检测到有异常发生。

PyErr_Fetch(PyObject** ptype, PyObject** pvalue, PyObject** ptraceback)

获得error indicator的三个变量,如果error indicator没有设置,ptype, pvalue, ptraceback都被设置为NULL。error indicator会被置空,将三个变量的地址赋给ptype, pvalue, ptraceback。

PyErr_Restore(PyObject* type, PyObject* value, PyObject* traceback)

使用给定的三个变量设置error indicator。

代码中使用异常接口的规则

如果函数f调用函数g,其中g函数失败,应该怎样设置异常呢?通常的做法是在g中调用设置异常的各个接口,比如PyErr_SetString,通知Python解释器有异常发生了,函数g然后返回一个NULL给函数f,而f不用再处理异常,因为函数g已经上报过了。比如我们自己写的函数调用了PyArg_ParseTuple(),这个函数出现错误时返回NULL,但是我们不用自己上报异常,异常的上报由PyArg_ParseTuple本身处理。一旦通过PyErr_SetString设置了异常,那么Python解释器在主循环中检测到error indicator被设置,会暂停执行当前的Python Code,会试图寻找exception handler来处理异常。

 

Python源码中使用异常处理接口的例子

PyTuple_GetItem:

PyObject *
PyTuple_GetItem(register PyObject *op, register Py_ssize_t i)
{
if (!PyTuple_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
if (i < || i >= Py_SIZE(op)) {
// 如果索引有错误,设置异常
PyErr_SetString(PyExc_IndexError, "tuple index out of range");
return NULL;
}
return ((PyTupleObject *)op) -> ob_item[i];
}

PyErr_SetString实现:

void
PyErr_SetString(PyObject *exception, const char *string)
{
PyObject *value = PyString_FromString(string);
PyErr_SetObject(exception, value);
Py_XDECREF(value);
}

PyErr_SetObject实现:

void
PyErr_SetObject(PyObject *exception, PyObject *value)
{
Py_XINCREF(exception); // 增加引用计数
Py_XINCREF(value); // 增加引用计数
PyErr_Restore(exception, value, (PyObject *)NULL);
}

PyErr_Restore实现:

void
PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
{
PyThreadState *tstate = PyThreadState_GET();
PyObject *oldtype, *oldvalue, *oldtraceback; if (traceback != NULL && !PyTraceBack_Check(traceback)) {
/* XXX Should never happen -- fatal error instead? */
/* Well, it could be None. */
Py_DECREF(traceback);
traceback = NULL;
} /* Save these in locals to safeguard against recursive
invocation through Py_XDECREF */
oldtype = tstate->curexc_type;
oldvalue = tstate->curexc_value;
oldtraceback = tstate->curexc_traceback;
// 设置当前的异常
tstate->curexc_type = type;
tstate->curexc_value = value;
tstate->curexc_traceback = traceback; // 旧的异常变量需要减少引用计数
Py_XDECREF(oldtype);
Py_XDECREF(oldvalue);
Py_XDECREF(oldtraceback);
}

自定义异常

除了使用Python已经定义好的异常对象之外,我们可以自定义异常类型,主要是通过PyErr_NewException创建一个异常对象。

1. 在文件头部定义一个static 变量:

  static PyObject *MyError;

2. 在模块初始化时传入我们自定义的异常对象

PyMODINIT_FUNC
inittest(void)
{
PyObject *m; m = Py_InitModule("test", TestMethods);
if (m == NULL)
return; MyError = PyErr_NewException("test.error", NULL, NULL);
Py_INCREF(MyError);
PyModule_AddObject(m, "error",MyError);
}

3. 在通过PyErr_SetString设置异常时,第一个参数传入MyError即可。

扩展Python模块系列(五)----异常和错误处理的更多相关文章

  1. 扩展Python模块系列(一)----开发环境配置

    本系列将介绍如何用C/C++扩展Python模块,使用C语言编写Python模块,添加到Python中作为一个built-in模块.Python与C之间的交互目前有几种方案: 1. 原生的Python ...

  2. 扩展Python模块系列(二)----一个简单的例子

    本节使用一个简单的例子引出Python C/C++ API的详细使用方法.针对的是CPython的解释器. 目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两 ...

  3. 扩展Python模块系列(四)----引用计数问题的处理

    承接上文,发现在使用Python C/C++ API扩展Python模块时,总要在各种各样的地方考虑到引用计数问题,稍不留神可能会导致扩展的模块存在内存泄漏.引用计数问题是C语言扩展Python模块最 ...

  4. 扩展Python模块系列(三)----参数解析与结果封装

    在上一节中,通过一个简单的例子介绍了C语言扩展Python内建模块的整体流程,从本节开始讲开始深入讨论一些细节问题,在细节讨论中从始至终都会涉及[引用计数]的问题.首先讨论C语言封装的Python函数 ...

  5. C语言扩展Python模块

    1. 先创建一个PythonDemo.cpp文件: //c/c++中调用python脚本,配置步骤参见上一篇:C/C++与python交互 \  C/C++中调用python文件. #include ...

  6. SpringBoot系列五:SpringBoot错误处理(数据验证、处理错误页、全局异常)

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念: SpringBoot 错误处理 2.具体内容 在之前的程序里面如果一旦出现了错误之后就会出现一堆的大白板,这个白板会 ...

  7. 【听如子说】-python模块系列-AIS编解码Pyais

    Pyais Module Introduce pyais一个简单实用的ais编解码模块 工作中需要和ais打交道,在摸鱼的过程中发现了一个牛逼的模块,对ais编解码感兴趣的可以拿项目学习一下,或者运用 ...

  8. python学习(五)--打印错误信息

    from urllib import request #打印错误信息 except Exceptionlist = [ "http://www.baidu11.com/", &qu ...

  9. [Head First Python]3. 文件与异常:处理错误

    datafile.txt Man: Is this the right room for an argument? Other Man: I've told you once. Man: No you ...

随机推荐

  1. (转)Maven项目标准目录结构

    场景:工作中的项目都是使用Maven的目录结构,虽然能够如期的完成开发工作,但是对于一个Maven工程的配置却并不熟悉,这里主要介绍下Maven工程的而目录结构. 1 标准目录结构 src -main ...

  2. (转)js jquery.qrcode生成二维码 带logo 支持中文

    场景:公司最最近在开发二维码支付业务,所以需要做一个html5中的二维码生成和部署! 前天用js生成二维码,节省服务器资源及带宽 原版jquery.qrcode不能生成logo,本文采用的是修改版 1 ...

  3. MySQL学习笔记(四):存储引擎的选择

    一:几种常用存储引擎汇总表 二:如何选择 一句话:除非需要InnoDB 不具备的特性,并且没有其他办法替代,否则都应该优先考虑InnoDB:或者,不需要InnoDB的特性,并且其他的引擎更加合适当前情 ...

  4. Linux(2)文件和权限

    用户目录 位于/home/user, 称为用户目录或家目录, 表示方法: /home/user ~ 相对路径和绝对路径 绝对路径 从 / 目录开始描述的路径外绝对路径 cd /home cd /usr ...

  5. JavaScript中正则表达式判断匹配规则以及常用的方法

    JavaScript中正则表达式判断匹配规则以及常用的方法: 字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在. 正则表达式是一种用来匹配字符串的强有力的武器.它的设计思想 ...

  6. Java集合框架Collections【List/Set】

    1.基本介绍: 集合就是存放对象的,他比数组好的一点就是他一开始不清楚自己长度 容器一般是分为很多种的,很多的容器在一起然后进过断的抽象和抽取就成了一个体系,我们称之为集合框架 我们看体系首先是看顶层 ...

  7. gitlab+jenkins持续集成(一)

    1. 环境:CentOS7.0,jdk-8u91-linux-x64.rpm,jenkins 2.7.4 ,gitlab 9.2.2 2.    安装jdk,jenkins     (rpm -ivh ...

  8. NYOJ--45--棋盘覆盖(大数)

    棋盘覆盖 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 在一个2k×2k(1<=k<=100)的棋盘中恰有一方格被覆盖,如图1(k=2时),现用一缺角的 ...

  9. Linux操作系统-命令-netstat

    # 之前已经写过了3篇与"性能测试"有关系的Linux命令,它们分别是free.top.vmstat # 接下来还需要把另外2个命令也写下来:netstat和iostat 最近认真地读了1篇关于"定位 ...

  10. IOS开发之UITabBarController与UINavigationController混合使用

    ios开发中UITabBarController与UINavigationController混合使用是很多app的基础页面结构,下面是简单的的页面初始化的方法,在AppDelegate.m的 - ( ...