Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 708|回复: 0

扩展Python模块系列(五)----异常和错误处理

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-7-19 10:34:34 | 显示全部楼层 |阅读模式

    在上一节中,讨论了在用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 < 0 || i >= Py_SIZE(op)) {
            // 如果索引有错误,设置异常
            PyErr_SetString(PyExc_IndexError, "tuple index out of range");
            return NULL;
        }
        return ((PyTupleObject *)op) -> ob_item;
    }

    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即可。

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-29 09:41 , Processed in 0.121163 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表