如果我们使用try... except...去捕获某个异常类型的话,就可以获取到上面创建的异常对象,并通过该对象中的args属性来获取具体的异常信息。如果没使用try...except来捕获异常的话,就将使用前面gdb调试中看到的sys_excepthook去处理异常,该C函数最后会通过PyErr_Display函数将traceback栈追踪信息,异常类型,异常的具体信息等,以标准错误输出的方式显示出来...

    页面导航: 前言:

    本篇文章由网友“小黑”投稿发布。

    本篇文章是根据《Python Tutorial》第17章的内容来写的学习笔记。该英文教程的下载地址如下:

    百度盘地址:http://pan.baidu.com/s/1c0eXSQG

    DropBox地址:点此进入DropBox链接

    Google Drive:点此进入Google Drive链接

    本篇文章也会涉及到Python的C源代码,这些C源码都是2.7.8版本的。想使用gdb来调试python源代码的话,就需要按照前面"Python基本的操作运算符"文章中所说的,使用configure --with-pydebug命令来重新编译安装python。

    如果Python的官方网站取消了Python-2.7.8.tgz的源码包的话,可以在以下两个链接中下载到:

    DropBox:Python-2.7.8.tgz的DropBox网盘链接

    Google Drive:Python-2.7.8.tgz的Google Drive网盘链接

    文章中的脚本代码主要是通过Slackware Linux系统中的python 2.7.8的版本来进行测试的。文章中的链接,可能都需要通过代理才能正常访问。

Python中的异常:

    下面是gdb对异常抛出过程的简单调试:

[email protected]:~/test/except$ gdb -q python
Reading symbols from /usr/local/bin/python...done.
(gdb) r
Starting program: /usr/local/bin/python 
[Thread debugging using libthread_db enabled]
Python 2.7.8 (default, Jan  1 2016, 17:52:25) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) b i_divmod
Breakpoint 1 at 0x807d42e: file Objects/intobject.c, line 588.
(gdb) b PyErr_SetString
Breakpoint 2 at 0x8109e2f: file Python/errors.c, line 69.
(gdb) b PyErr_NormalizeException
Breakpoint 3 at 0x810a0b5: file Python/errors.c, line 147.
(gdb) b BaseException_new
Breakpoint 4 at 0x806c062: file Objects/exceptions.c, line 34.
(gdb) b BaseException_init
Breakpoint 5 at 0x806c191: file Objects/exceptions.c, line 58.
(gdb) b sys_excepthook
Breakpoint 6 at 0x812809d: file Python/sysmodule.c, line 136.
(gdb) c
Continuing.

[40779 refs]
>>> 5 / 0

Breakpoint 1, i_divmod (x=5, y=0, p_xdivy=0xbfffeea4, p_xmody=0xbfffeea0)
    at Objects/intobject.c:588
588	    if (y == 0) {
(gdb) c
Continuing.

Breakpoint 2, PyErr_SetString (exception=0x81c0de0, 
    string=0x8198888 "integer division or modulo by zero")
    at Python/errors.c:69
69	    PyObject *value = PyString_FromString(string);
(gdb) n
70	    PyErr_SetObject(exception, value);
(gdb) s
PyErr_SetObject (exception=0x81c0de0, value=0xb7d4b758) at Python/errors.c:55
55	    Py_XINCREF(exception);
(gdb) n
56	    Py_XINCREF(value);
(gdb) n
57	    PyErr_Restore(exception, value, (PyObject *)NULL);
(gdb) s
PyErr_Restore (type=0x81c0de0, value=0xb7d4b758, traceback=0x0)
    at Python/errors.c:27
27	    PyThreadState *tstate = PyThreadState_GET();
(gdb) c
Continuing.

Breakpoint 3, PyErr_NormalizeException (exc=0xbffff210, val=0xbffff20c, 
    tb=0xbffff208) at Python/errors.c:147
147	    PyObject *type = *exc;
(gdb) c
Continuing.

Breakpoint 4, BaseException_new (type=0x81c0de0, args=0xb7d38494, kwds=0x0)
    at Objects/exceptions.c:34
warning: Source file is more recent than executable.
34	    self = (PyBaseExceptionObject *)type->tp_alloc(type, 0);
(gdb) c
Continuing.

Breakpoint 5, BaseException_init (self=0xb7ce16c4, args=0xb7d38494, kwds=0x0)
    at Objects/exceptions.c:58
58	    if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds))
(gdb) c
Continuing.

Breakpoint 6, sys_excepthook (self=0x0, args=0xb7ce2134)
    at Python/sysmodule.c:136
136	    if (!PyArg_UnpackTuple(args, "excepthook", 3, 3, &exc, &value, &tb))
(gdb) n
138	    PyErr_Display(exc, value, tb);
(gdb) s
PyErr_Display (exception=0x81c0de0, value=0xb7ce16c4, tb=0xb7ce20b4)
    at Python/pythonrun.c:1230
1230	    int err = 0;
(gdb) c
Continuing.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
[40805 refs]
>>> 


    通过调试可以看到,5 / 0在执行时,会通过底层的i_divmod去执行除法运算(上面的gdb调试过程,已经显示出了该C函数所在的文件位置):

static enum divmod_result
i_divmod(register long x, register long y,
         long *p_xdivy, long *p_xmody)
{
    long xdivy, xmody;

    if (y == 0) {
        PyErr_SetString(PyExc_ZeroDivisionError,
                        "integer division or modulo by zero");
        return DIVMOD_ERROR;
    }
    ................................................
}


    当除数为0时,i_divmod会通过PyErr_SetString来抛出ZeroDivisionError异常,"integer division or modulo by zero"是与异常相关的具体信息,这里表示整数除零错误。

    PyErr_SetString会进入到PyErr_SetObject,并通过该函数进入到PyErr_Restore:

void
PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
{
    PyThreadState *tstate = PyThreadState_GET();
    PyObject *oldtype, *oldvalue, *oldtraceback;

    ................................................

    tstate->curexc_type = type;
    tstate->curexc_value = value;
    tstate->curexc_traceback = traceback;

    ................................................
}


    该函数会将type(异常类型,例如ZeroDivisionError类型),value(异常信息,如"integer division or modulo by zero"),以及traceback(发生异常时的栈追踪信息),保存到当前线程的tstate结构中。后面在BaseException_new函数中,会利用tstate中保存的异常类型,去创建具体的异常对象实例,并通过BaseException_init来初始化该对象实例:

/*
 *    BaseException
 */
// 通过BaseException_new创建新的异常对象。
static PyObject *
BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyBaseExceptionObject *self;

    // 根据type类型,为新的异常对象实例,分配堆空间。
    self = (PyBaseExceptionObject *)type->tp_alloc(type, 0);
    if (!self)
        return NULL;
    /* the dict is created on the fly in PyObject_GenericSetAttr */
    self->message = self->dict = NULL;

    // 与异常相关的信息都存储在异常对象实例里的args元组中,
    // 例如,"integer division or modulo by zero"之类的异常信息。
    self->args = PyTuple_New(0);
    if (!self->args) {
        Py_DECREF(self);
        return NULL;
    }

    // 异常对象中的message属性是一个被弃用的属性,
    // 当args元组的大小为1时,message将被设置为args[0]
    self->message = PyString_FromString("");
    if (!self->message) {
        Py_DECREF(self);
        return NULL;
    }

    return (PyObject *)self;
}

// 通过BaseException_init来对异常对象中的args及message属性进行初始化。
static int
BaseException_init(PyBaseExceptionObject *self, PyObject *args, PyObject *kwds)
{
    if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds))
        return -1;

    Py_DECREF(self->args);
    // 将异常相关的信息都存储到args元组中。
    // 我们可以给一个异常设置很多个异常信息。
    // 例如errno(错误代码), errstr(错误相关的字符串信息)等,
    // 每个异常信息都将成为args元组中的一个成员。
    self->args = args;
    Py_INCREF(self->args);

    // 只有当args元组的尺寸大小为1时,才会去设置message属性。
    // 下面会将args[0]设置为message。
    // message是一个从python 2.6开始,deprecated(被弃用)了的属性。
    // 在python 2.6.x中使用message属性可能会抛出DeprecationWarning警告。
    // 在python 2.7.8中,DeprecationWarning警告默认被ignore(忽略掉了)。
    if (PyTuple_GET_SIZE(self->args) == 1) {
        Py_CLEAR(self->message);
        self->message = PyTuple_GET_ITEM(self->args, 0);
        Py_INCREF(self->message);
    }
    return 0;
}


    如果我们使用try... except...去捕获某个异常类型的话,就可以获取到上面创建的异常对象,并通过该对象中的args属性来获取具体的异常信息。如果没使用try...except来捕获异常的话,就将使用前面gdb调试中看到的sys_excepthook去处理异常,该C函数最后会通过PyErr_Display函数将traceback栈追踪信息,异常类型,异常的具体信息等,以标准错误输出的方式显示出来。

    下面是try...except...的最基本的语法格式(更多的语法格式,将在后面再进行介绍):

try:
	try_code...
except ExceptionType [as|,] errobj:
	exception_code...


    ExceptionType表示异常类型,异常类型后面可以通过as或者,(逗号),再接一个errobj,当设置了errobj时,python会将异常对象实例赋值给errobj变量。例如下面这个脚本:

# zero_except.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	5 / 0
except ZeroDivisionError:
	print 'catch Zero Division Error use ZeroDivisionError\n'

try:
	5 / 0
except ZeroDivisionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj message:', myerrobj.message
	print 'the following is traceback:'
	view_traceback()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python zero_except.py 
catch Zero Division Error use ZeroDivisionError

catch <type 'exceptions.ZeroDivisionError'>
err obj args: ('integer division or modulo by zero',)
err obj message: integer division or modulo by zero
the following is traceback:
  File "zero_except.py", line 16, in <module>
    5 / 0
[18364 refs]
[email protected]:~/test/except$ 


    脚本中通过traceback模块来显示出栈追踪信息。该模块的print_tb方法需要接受traceback object作为输入参数,traceback object可以通过sys.exc_info()来获取。sys.exc_info()会返回一个元组,该元组的第一项用于存储异常类型,第二项存储异常相关的信息,第三项存储traceback object。当在函数中使用完sys.exc_info()返回的traceback object后,需要通过del来移出局部变量对traceback object的引用,以防止出现circular reference(循环引用),循环引用在后面讲解ReferenceError时会进行介绍。

    ZeroDivisionError异常类型定义在Python源码里的Objects/exceptions.c文件中:

/*
 *    ArithmeticError extends StandardError
 */
SimpleExtendsException(PyExc_StandardError, ArithmeticError,
                       "Base class for arithmetic errors.");
....................................................
/*
 *    ZeroDivisionError extends ArithmeticError
 */
SimpleExtendsException(PyExc_ArithmeticError, ZeroDivisionError,
          "Second argument to a division or modulo operation was zero.");


    ZeroDivisionError是ArithmeticError类型的子类,ArithmeticError又是StandardError的子类。try...except结构中可以通过基类来捕获子类的异常对象:

# zero_except2.py
try:
	5 / 0
except ZeroDivisionError as myerrobj:
	print 'catch', type(myerrobj), 'use ZeroDivisionError'

try:
	5 / 0
except ArithmeticError as myerrobj:
	print 'catch', type(myerrobj), 'use ArithmeticError'

try:
	5 / 0
except StandardError as myerrobj:
	print 'catch', type(myerrobj), 'use StandardError'

try:
	5 / 0
except OverflowError as myerrobj:
	print 'this will not happen'


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python zero_except2.py 
catch <type 'exceptions.ZeroDivisionError'> use ZeroDivisionError
catch <type 'exceptions.ZeroDivisionError'> use ArithmeticError
catch <type 'exceptions.ZeroDivisionError'> use StandardError
Traceback (most recent call last):
  File "zero_except2.py", line 18, in <module>
    5 / 0
ZeroDivisionError: integer division or modulo by zero
[18363 refs]
[email protected]:~/test/except$ 


    上面的OverflowError与ZeroDivisionError不存在继承关系,因此,无法使用OverflowError来捕获ZeroDivisionError类型的异常对象。

    在Python源码的Include/pyerrors.h头文件中,可以看到python 2.7.8中所有的异常类型的相关声明:

/* 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;
#ifdef MS_WINDOWS
PyAPI_DATA(PyObject *) PyExc_WindowsError;
#endif
#ifdef __VMS
PyAPI_DATA(PyObject *) PyExc_VMSError;
#endif

PyAPI_DATA(PyObject *) PyExc_BufferError;

PyAPI_DATA(PyObject *) PyExc_MemoryErrorInst;
PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst;

/* Predefined warning categories */
PyAPI_DATA(PyObject *) PyExc_Warning;
PyAPI_DATA(PyObject *) PyExc_UserWarning;
PyAPI_DATA(PyObject *) PyExc_DeprecationWarning;
PyAPI_DATA(PyObject *) PyExc_PendingDeprecationWarning;
PyAPI_DATA(PyObject *) PyExc_SyntaxWarning;
PyAPI_DATA(PyObject *) PyExc_RuntimeWarning;
PyAPI_DATA(PyObject *) PyExc_FutureWarning;
PyAPI_DATA(PyObject *) PyExc_ImportWarning;
PyAPI_DATA(PyObject *) PyExc_UnicodeWarning;
PyAPI_DATA(PyObject *) PyExc_BytesWarning;


    这些异常类型,会在python初始化时,通过_PyExc_Init的C函数将它们都添加到__builtin__的内建模块中:

#define POST_INIT(TYPE) Py_INCREF(PyExc_ ## TYPE); \
    PyModule_AddObject(m, # TYPE, PyExc_ ## TYPE); \
    if (PyDict_SetItemString(bdict, # TYPE, PyExc_ ## TYPE)) \
        Py_FatalError("Module dictionary insertion problem.");


PyMODINIT_FUNC
_PyExc_Init(void)
{
    PyObject *m, *bltinmod, *bdict;
    ................................................
    // 获取__builtin__内建模块
    bltinmod = PyImport_ImportModule("__builtin__");
    if (bltinmod == NULL)
        Py_FatalError("exceptions bootstrapping error.");
    // 获取__builtin__内建模块的属性词典。
    bdict = PyModule_GetDict(bltinmod);
    if (bdict == NULL)
        Py_FatalError("exceptions bootstrapping error.");

    // 通过POST_INIT宏,将异常类型添加到内建模块的属性词典中,
    // 这样,这些异常类型就会成为内建模块的属性成员了。
    POST_INIT(BaseException)
    POST_INIT(Exception)
    POST_INIT(StandardError)
    POST_INIT(TypeError)
    POST_INIT(StopIteration)
    POST_INIT(GeneratorExit)
    POST_INIT(SystemExit)
    POST_INIT(KeyboardInterrupt)
    POST_INIT(ImportError)
    POST_INIT(EnvironmentError)
    ................................................
}


    我们可以通过dir(__builtins__)来查看到内建模块里的这些异常类型:

[email protected]:~/test/except$ python
Python 2.7.8 (default, Jan  1 2016, 17:52:25) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 
 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 
 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 
 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 
 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 
 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 
 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 
 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 
 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 
 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 
 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 
 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', 
 ..............................................................]
[40956 refs]
>>> 


    在 https://docs.python.org/2/library/exceptions.html?highlight=exception#exception-hierarchy 的官方手册中,可以看到这些异常类型的相互之间的继承关系:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning


    下面就对这些异常类型进行介绍。

BaseException:

    BaseException: The base class for all built-in exceptions(New in version 2.5) 所有内建异常的基类( 在Python 2.5版本中新增的)。在Python 2.5版本之前,Exception是所有异常的基类,2.5版本后,Exception变为BaseException的子类,BaseException则成为所有内建异常的基类。

    之所以要引入BaseException,主要是为了能够更好的规划异常,比如,将SystemExit从Exception中分离出来,从而让sys.exit方法在执行时,不会受到Exception的影响,等等。

    BaseException的异常对象实例的内部C结构体,定义在Include/pyerrors.h的头文件中:

/* Error objects */

typedef struct {
    PyObject_HEAD
    PyObject *dict;
    PyObject *args;
    PyObject *message;
} PyBaseExceptionObject;


    任何一个从BaseException继承下来的异常,它们的实例对象中都会包含args和message属性(message已被弃用):

# base_except.py
try:
	5 / 0
except ZeroDivisionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj message:', myerrobj.message

print

try:
	5 / 0
except BaseException as myerrobj:
	print 'catch', type(myerrobj), 'use BaseException:'
	print 'err obj args:', myerrobj.args
	print 'err obj message:', myerrobj.message

print

try:
	open('not_exists')
except IOError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj message:', myerrobj.message

print

try:
	open('not_exists')
except BaseException as myerrobj:
	print 'catch', type(myerrobj), 'use BaseException:'
	print 'err obj args:', myerrobj.args
	print 'err obj message:', myerrobj.message


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python base_except.py 
catch <type 'exceptions.ZeroDivisionError'>
err obj args: ('integer division or modulo by zero',)
err obj message: integer division or modulo by zero

catch <type 'exceptions.ZeroDivisionError'> use BaseException:
err obj args: ('integer division or modulo by zero',)
err obj message: integer division or modulo by zero

catch <type 'exceptions.IOError'>
err obj args: (2, 'No such file or directory')
err obj message: 

catch <type 'exceptions.IOError'> use BaseException:
err obj args: (2, 'No such file or directory')
err obj message: 
[18364 refs]
[email protected]:~/test/except$ 


    不过官方手册不建议,用户自定义的异常类型,直接从BaseException类进行继承,而应该从Exception类进行继承(下面介绍Exception时会看到相关的例子)。

SystemExit:

    当执行sys.exit方法时,就会设置SystemExit异常(下面的C源码定义在Python/sysmodule.c文件中):

static PyObject *
sys_exit(PyObject *self, PyObject *args)
{
    PyObject *exit_code = 0;
    if (!PyArg_UnpackTuple(args, "exit", 0, 1, &exit_code))
        return NULL;
    /* Raise SystemExit so callers may catch it or clean up. */
    PyErr_SetObject(PyExc_SystemExit, exit_code);
    return NULL;
}

PyDoc_STRVAR(exit_doc,
"exit([status])\n\
\n\
Exit the interpreter by raising SystemExit(status).\n\
If the status is omitted or None, it defaults to zero (i.e., success).\n\
If the status is an integer, it will be used as the system exit status.\n\
If it is another kind of object, it will be printed and the system\n\
exit status will be one (i.e., failure)."
);


    下面是与SystemExit异常对象相关的C结构(也定义在Include/pyerrors.h的头文件中):

typedef struct {
    PyObject_HEAD
    PyObject *dict;
    PyObject *args;
    PyObject *message;
    PyObject *code;
} PySystemExitObject;


    可以看到,除了args,message外,它还多了一个code参数,可以表示退出状态码,也可以表示退出时需要显示的字符串信息:

# systemexit.py 
import sys
try:
	sys.exit(-1)
except SystemExit as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj code:', myerrobj.code

print

try:
	sys.exit('hello world')
except SystemExit as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj code:', myerrobj.code


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python systemexit.py 
catch <type 'exceptions.SystemExit'>
err obj args: (-1,)
err obj code: -1

catch <type 'exceptions.SystemExit'>
err obj args: ('hello world',)
err obj code: hello world
[18363 refs]
[email protected]:~/test/except$ 


KeyboardInterrupt:

    下面是和KeyboardInterrupt异常相关的C代码(定义在Python/pythonrun.c文件中):

/* Set the error appropriate to the given input error code (see errcode.h) */
static void
err_input(perrdetail *err)
{
    ...........................................
    case E_INTR:
        if (!PyErr_Occurred())
            PyErr_SetNone(PyExc_KeyboardInterrupt);
        goto cleanup;
    ...........................................
}


    当用户在终端里按下ctrl + c产生键盘中断信号时,会抛出KeyboardInterrupt异常。PyErr_SetNone表示,它不会给该异常的对象实例设置任何参数。例如下面这个脚本:

# keyint.py
try:
	raw_input('please press ctrl+c:\n')
except KeyboardInterrupt as myerrobj:
	print '\ncatch', type(myerrobj)
	print 'err obj args:', myerrobj.args


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python keyint.py 
please press ctrl+c:
^C
catch <type 'exceptions.KeyboardInterrupt'>
err obj args: ()
[18363 refs]
[email protected]:~/test/except$ 


GeneratorExit:

    该异常主要用于generator对象的close方法,该方法的底层C函数如下(定义在Objects/genobject.c文件中):

PyDoc_STRVAR(close_doc,
"close() -> raise GeneratorExit inside generator.");

static PyObject *
gen_close(PyGenObject *gen, PyObject *args)
{
    PyObject *retval;
    PyErr_SetNone(PyExc_GeneratorExit);
    // close方法会向generator所对应的脚步函数,发送GeneratorExit异常。
    retval = gen_send_ex(gen, Py_None, 1); 
    if (retval) {
        Py_DECREF(retval);
        PyErr_SetString(PyExc_RuntimeError,
                        "generator ignored GeneratorExit");
        return NULL;
    }
    if (PyErr_ExceptionMatches(PyExc_StopIteration)
        || PyErr_ExceptionMatches(PyExc_GeneratorExit))
    {
        PyErr_Clear();          /* ignore these errors */
        Py_INCREF(Py_None);
        return Py_None;
    }
    return NULL;
}


    例如下面这个脚本:

# gen_except.py
def myfunc():
	try:
		for i in range(0, 10):
			yield i
	except GeneratorExit as myerrobj:
		print 'catch GeneratorExit in myfunc'
		print 'err obj args:', myerrobj.args

genobj = myfunc()

for x in genobj:
	print x
	if(x == 4):
		genobj.close()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python gen_except.py 
0
1
2
3
4
catch GeneratorExit in myfunc
err obj args: ()
[18363 refs]
[email protected]:~/test/except$ 


    上面在genobj.close()执行时,就会向关联的myfunc发送GeneratorExit异常。

Exception:

    Exception:在2.5版本之前,所有内建的异常都继承自Exception。在2.5版本之后,与系统退出相关的异常如SystemExit,KeyboardInterrupt以及GeneratorExit都分离了出去(它们继承自BaseException了)。

    其他的内建异常,还是继续继承自Exception,同时Exception自己也继承自BaseException。

    官方手册上说,用户自定义的异常,应该从Exception进行继承(不应该从BaseException进行继承)。例如下面这个脚本:

# exception.py
class mydefined_except(Exception):
	def __init__(self, mycode, msg):
		self.args = (mycode, msg)
		self.mycode = mycode
		self.msg = msg

try:
	raise mydefined_except(101, 'this is my defined exception derived from Exception')
except Exception as myerrobj:
	print 'catch mydefined_except use base class "Exception":'
	print 'err obj args:', myerrobj.args
	print 'err obj mycode:', myerrobj.mycode
	print 'err obj msg:', myerrobj.msg


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python exception.py 
catch mydefined_except use base class "Exception":
err obj args: (101, 'this is my defined exception derived from Exception')
err obj mycode: 101
err obj msg: this is my defined exception derived from Exception
[18424 refs]
[email protected]:~/test/except$ 


StopIteration:

    该异常主要用于对迭代器使用next方法时,如果迭代操作超出范围,就会抛出该异常。下面是与next方法相关的内部C函数(定义在Python/bltinmodule.c文件中):

static PyObject *
builtin_next(PyObject *self, PyObject *args)
{
    PyObject *it, *res;
    PyObject *def = NULL;

    if (!PyArg_UnpackTuple(args, "next", 1, 2, &it, &def))
        return NULL;
    ................................................

    res = (*it->ob_type->tp_iternext)(it);
    if (res != NULL) {
        return res;
    } else if (def != NULL) {
        ............................................
        Py_INCREF(def);
        return def;
    } else if (PyErr_Occurred()) {
        return NULL;
    } else {
        // 如果迭代操作,超出了对象的可迭代的范围,
	// 同时又没有设置default参数时,
	// 就会抛出StopIteration异常。
        PyErr_SetNone(PyExc_StopIteration);
        return NULL;
    }
}

PyDoc_STRVAR(next_doc,
"next(iterator[, default])\n\
\n\
Return the next item from the iterator. If default is given and the iterator\n\
is exhausted, it is returned instead of raising StopIteration.");


    例如下面这个脚本:

# StopIteration.py
mydict = {'hello':123, 'world':456}

iterator = mydict.iterkeys()
while(True):
	try:
		print next(iterator)
	except StopIteration as myerrobj:
		print 'catch', type(myerrobj)
		print 'err obj args:', myerrobj.args
		break


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python StopIteration.py 
world
hello
catch <type 'exceptions.StopIteration'>
err obj args: ()
[18363 refs]
[email protected]:~/test/except$ 


StandardError:

    很多与错误相关的异常都继承自StandardError:

StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError


    例如下面这个脚本:

# standerror.py
try:
	lst[0]
except StandardError as myerrobj:
	print 'catch', type(myerrobj), 'use StandardError'

try:
	lst = [1,2,3]
	print lst[4]
except StandardError as myerrobj:
	print 'catch', type(myerrobj), 'use StandardError'

try:
	5 / 0
except StandardError as myerrobj:
	print 'catch', type(myerrobj), 'use StandardError'

try:
	open('not_exists')
except StandardError as myerrobj:
	print 'catch', type(myerrobj), 'use StandardError'


    脚本的执行结果如下:

[email protected]:~/test/except$ python standerror.py 
catch <type 'exceptions.NameError'> use StandardError
catch <type 'exceptions.IndexError'> use StandardError
catch <type 'exceptions.ZeroDivisionError'> use StandardError
catch <type 'exceptions.IOError'> use StandardError
[18363 refs]
[email protected]:~/test/except$ 


BufferError:

    与buffer缓冲相关的操作发生异常时,就会抛出BufferError异常。

    例如,bytearray字节数组就是Python中的一种buffer。

    下面是操作bytearray时,与BufferError相关的C代码(这段代码定义在Objects/bytearrayobject.c文件中):

static int
_canresize(PyByteArrayObject *self)
{
    if (self->ob_exports > 0) {
        PyErr_SetString(PyExc_BufferError,
                "Existing exports of data: object cannot be re-sized");
        return 0;
    }
    return 1;
}


    以下是相关的测试脚本:

# BufferError.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	b = bytearray(range(10))
	m = memoryview(b)
	b[1:8] = b'X'
except BufferError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python BufferError.py 
catch <type 'exceptions.BufferError'>
err obj args: ('Existing exports of data: object cannot be re-sized',)
the following is traceback:
  File "BufferError.py", line 13, in <module>
    b[1:8] = b'X'
[18363 refs]
[email protected]:~/test/except$ 


    当为bytearray创建一个memoryview对象后,bytearray内部的ob_exports的值就会变为1,此时再对其进行resize调整大小的操作,就会抛出BufferError的异常。

    memoryview可以为支持buffer protocol(缓冲协议)的对象创建一个视图,通过该视图,Python代码可以直接访问到对象内部的数据,并且不需要对原对象里的数据进行额外的拷贝操作。

    有关buffer protocol的概念可以参考 https://jakevdp.github.io/blog/2014/05/05/introduction-to-the-python-buffer-protocol/ 该链接对应的文章。

ArithmeticError:

    与数学计算相关的错误,一般都继承自ArithmeticError:

ArithmeticError
     |    +-- FloatingPointError
     |    +-- OverflowError
     |    +-- ZeroDivisionError


    下面是一个简单的测试脚本:

# ArithError.py 
try:
	import math
	math.exp(1000)
except ArithmeticError as myerrobj:
	print 'catch', type(myerrobj), 'use ArithmeticError'

try:
	5 / 0
except ArithmeticError as myerrobj:
	print 'catch', type(myerrobj), 'use ArithmeticError'


    脚本的执行结果如下:

[email protected]:~/test/except$ python ArithError.py 
catch <type 'exceptions.OverflowError'> use ArithmeticError
catch <type 'exceptions.ZeroDivisionError'> use ArithmeticError
[18363 refs]
[email protected]:~/test/except$ 


FloatingPointError:

    FloatingPointError:与浮点计算相关的错误。

    要测试FloatingPointError异常,需要在编译Python时设置--with-fpectl选项,此外,还需要先在Python源代码的setup.py脚本中将fpectl模块相关的C文件添加到扩展模块列表中(默认情况下,Python不会去编译fpectl模块相关的C文件):

setup.py 657:

        # select(2); not on ancient System V
        exts.append( Extension('select', ['selectmodule.c']) )

        # fpectl add by myself
        exts.append( Extension('fpectl', ['fpectlmodule.c']) )


    在我的Python源码的根目录中的setup.py脚本的第657行,我在select模块的下方,将fpectl模块及相关的fpectlmodule.c添加到扩展模块列表中,这样,下面编译时,才会去编译fpectlmodule.c文件。

    接着就可以使用--with-fpectl选项对Python进行重新编译:

[email protected]:~/Downloads/Python-2.7.8$ make clean
[email protected]:~/Downloads/Python-2.7.8$ ./configure --with-pydebug --with-fpectl
[email protected]:~/Downloads/Python-2.7.8$ make
[email protected]:~/Downloads/Python-2.7.8$ sudo make install


    编译安装好fpectl模块后,就可以使用该模块来测试下面这个脚本:

# FloatError.py 
import fpectl, traceback, sys

fpectl.turnon_sigfpe()

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	import math
	math.exp(1000)
except FloatingPointError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    该脚本的执行结果如下:

[email protected]:~/test/except$ python FloatError.py 
catch <type 'exceptions.FloatingPointError'>
err obj args: ('in math_1',)
the following is traceback:
  File "FloatError.py", line 13, in <module>
    math.exp(1000)
[18383 refs]
[email protected]:~/test/except$ 


    math.exp(1000)在执行时,会在内部产生SIGFPE信号。脚本的开头,通过执行fpectl.turnon_sigfpe方法,就可以让fpectl模块捕获到SIGFPE信号。fpectl模块在处理该信号时,内部会通过长跳转的方式,跳转到发生异常的C代码位置处,然后抛出FloatingPointError异常。如果不执行fpectl.turnon_sigfpe方法的话,fpectl模块就无法捕获SIGFPE信号,那么默认就会抛出OverflowError异常。

    下面是与math.exp方法相关的内部C代码:

Include/pyfpe.h 136:

#define PyFPE_START_PROTECT(err_string, leave_stmt) \
if (!PyFPE_counter++ && setjmp(PyFPE_jbuf)) { \
	PyErr_SetString(PyExc_FloatingPointError, err_string); \
	PyFPE_counter = 0; \
	leave_stmt; \
}

Modules/mathmodule.c 683:

static PyObject *
math_1(PyObject *arg, double (*func) (double), int can_overflow)
{
    double x, r;
    x = PyFloat_AsDouble(arg);
    if (x == -1.0 && PyErr_Occurred())
        return NULL;
    errno = 0;
    // 下面的PyFPE_START_PROTECT宏会通过setjmp
    // 为fpectl模块设置好长跳转的目标位置。
    // 一旦math模块里的数学函数,因浮点计算出错而产生SIGFPE信号时,
    // fpectl模块就可以捕获到该信号,并长跳转到这里,
    // 最后通过上面PyFPE_START_PROTECT宏定义中的
    // PyErr_SetString(PyExc_FloatingPointError, err_string);
    // 去抛出FloatingPointError异常。
    PyFPE_START_PROTECT("in math_1", return 0);
    r = (*func)(x);
    PyFPE_END_PROTECT(r);
    ...............................
}


    上面标注了math_1与PyFPE_START_PROTECT宏,所在的文件及行号的信息(当然,是针对于Python 2.7.8版本的源代码)。

    与fpectl模块相关的更多内容,可以参考 https://docs.python.org/2/library/fpectl.html?highlight=fpectl#module-fpectl 该链接对应的官方手册。

OverflowError:

    OverflowError:当某项操作,导致结果超出了指定范围时,就会抛出OverflowError异常。

    以下是range内建函数中,与OverflowError异常相关的C代码(定义在Python/bltinmodule.c文件中):

/* An extension of builtin_range() that handles the case when PyLong
 * arguments are given. */
static PyObject *
handle_range_longs(PyObject *self, PyObject *args)
{
    ...................................................
    if (bign < 0 || (long)n != bign) {
        PyErr_SetString(PyExc_OverflowError,
                        "range() result has too many items");
        goto Fail;
    }
    ...................................................
}


    当提供给range的参数,会导致结果超出范围时,就会抛出OverflowError异常。例如下面这个脚本:

# Overflow.py 
import traceback, sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	range(0,100000000000000000)
except OverflowError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python Overflow.py 
catch <type 'exceptions.OverflowError'>
err obj args: ('range() result has too many items',)
the following is traceback:
  File "Overflow.py", line 10, in <module>
    range(0,100000000000000000)
[18363 refs]
[email protected]:~/test/except$ 


    浮点数的指数运算,也会产生OverflowError,下面是与浮点数的指数运算相关的C代码(定义在Objects/floatobject.c文件中):

static PyObject *
float_pow(PyObject *v, PyObject *w, PyObject *z)
{
    ...................................................
    PyFPE_START_PROTECT("pow", return NULL)
    ix = pow(iv, iw);
    PyFPE_END_PROTECT(ix)
    Py_ADJUST_ERANGE1(ix);
    if (negate_result)
        ix = -ix;

    if (errno != 0) {
        /* We don't expect any errno value other than ERANGE, but
         * the range of libm bugs appears unbounded.
         */
        // 后面测试用的Overflow2.py脚本中,2.0 ** 90(计算2.0的90次方),
        // 以及f ** 2,这些浮点数的指数运算,最后都会进入到当前的float_pow函数中,
        // 并通过pow数学库函数去执行具体的操作,如果浮点计算发生溢出的话,
        // pow就会将errno设置为ERANGE,下面就会通过PyErr_SetFromErrno
        // 去抛出OverflowError异常,在PyErr_SetFromErrno的内部,
        // 会使用strerror的C库函数,根据ERANGE错误码,生成具体的错误信息,
        // 最后生成的OverflowError异常对象里的args元组中,就将包含两个成员,
        // 一个是ERANGE错误码,另一个则是strerror生成的具体的错误信息。
        PyErr_SetFromErrno(errno == ERANGE ? PyExc_OverflowError :
                             PyExc_ValueError);
        return NULL;
    }
    return PyFloat_FromDouble(ix);
}


    以下是测试脚本:

# Overflow2.py 
import traceback, sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	f = 2.0 ** 90
	for i in range(100):
		print i, f
		f = f ** 2
except OverflowError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python Overflow2.py
0 1.23794003929e+27
1 1.53249554087e+54
2 2.34854258277e+108
3 5.5156522631e+216
catch <type 'exceptions.OverflowError'>
err obj args: (34, 'Numerical result out of range')
the following is traceback:
  File "Overflow2.py", line 13, in <module>
    f = f ** 2
[18363 refs]
[email protected]:~/test/except$ 


    上面的34就是前面C源码中提到过的ERANGE错误码,'Numerical result out of range'则是由strerror根据ERANGE错误码所生成的具体的错误信息。

ZeroDivisionError:

    当除数为0时,就会抛出ZeroDivisionError异常。

    除了整数除法运算的除数为0时,会抛出该异常外。浮点数在进行除法运算时,如果除数是0,也会抛出ZeroDivisionError。以下是与浮点数的除法运算相关的C代码(定义在Objects/floatobject.c文件中):

static PyObject *
float_div(PyObject *v, PyObject *w)
{
    double a,b;
    CONVERT_TO_DOUBLE(v, a);
    CONVERT_TO_DOUBLE(w, b);
#ifdef Py_NAN
    if (b == 0.0) {
        // 当浮点数的除法运算的除数为0时,
        // 就会抛出ZeroDivisionError异常。
        PyErr_SetString(PyExc_ZeroDivisionError,
                        "float division by zero");
        return NULL;
    }
#endif
    PyFPE_START_PROTECT("divide", return 0)
    a = a / b;
    PyFPE_END_PROTECT(a)
    return PyFloat_FromDouble(a);
}


    以下是简单的测试脚本:

# zero_except3.py 
import traceback, sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	5 / 0
except ZeroDivisionError as myerrobj:
	print 'catch ZeroDivisionError info:'
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()

print 

try:
	2.0 / 0.0
except ZeroDivisionError as myerrobj:
	print 'catch ZeroDivisionError info:'
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python zero_except3.py 
catch ZeroDivisionError info:
err obj args: ('integer division or modulo by zero',)
the following is traceback:
  File "zero_except3.py", line 10, in <module>
    5 / 0

catch ZeroDivisionError info:
err obj args: ('float division by zero',)
the following is traceback:
  File "zero_except3.py", line 20, in <module>
    2.0 / 0.0
[18363 refs]
[email protected]:~/test/except$ 


AssertionError:

    AssertionError:主要用于assert语句。assert语句的语法格式如下:

assert Expression[, Arguments]

    当assert后面的Expression(表达式)执行的结果为False时,就会抛出AssertionError异常。例如下面这个脚本:

# AssertError.py 
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	assert (1 == 0), '1 not equal to 0'
except AssertionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()

print
try:
	assert False, 'Expression is False'
except AssertionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args

print
try:
	assert 0, 'Expression is 0'
except AssertionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args

print
try:
	assert True, "I'll never be catched"
	print 'True pass the assert'
except AssertionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args

print
try:
	assert 101, "I'll never be catched"
	print '101 pass the assert'
except AssertionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args


    该脚本的执行结果如下:

[email protected]:~/test/except$ python AssertError.py 
catch <type 'exceptions.AssertionError'>
err obj args: ('1 not equal to 0',)
the following is traceback:
  File "AssertError.py", line 11, in <module>
    assert (1 == 0), '1 not equal to 0'

catch <type 'exceptions.AssertionError'>
err obj args: ('Expression is False',)

catch <type 'exceptions.AssertionError'>
err obj args: ('Expression is 0',)

True pass the assert

101 pass the assert
[18364 refs]
[email protected]:~/test/except$ 


    如果表达式的结果为False,或者是0,都会触发AssertionError。如果表达式的结果是一个非0的数(例如上面的101),则相当于True,此时就可以通过检测而不抛出异常。

    很多模块也会在某些情况下,通过raise来主动抛出AssertionError的异常:

/usr/local/lib/python2.7/unittest/case.py:

class TestCase(object):
    .................................................
    failureException = AssertionError
    .................................................
    def assertFalse(self, expr, msg=None):
        """Check that the expression is false."""
        if expr:
            msg = self._formatMessage(msg, "%s is not false" % safe_repr(expr))
            raise self.failureException(msg)

    def assertTrue(self, expr, msg=None):
        """Check that the expression is true."""
        if not expr:
            msg = self._formatMessage(msg, "%s is not true" % safe_repr(expr))
            raise self.failureException(msg)


    在unittest/case.py文件中,TestCase类包含assertFalse与assertTrue方法。assertFalse用于检测expr表达式的结果,是否是False,如果不是就抛出AssertionError异常。assertTrue则反过来,用于检测expr表达式的结果,是否是True,如果不是则抛出AssertionError异常。

    例如下面这个脚本:

# AssertError2.py 
import unittest
import traceback
import sys

class MyTestCase(unittest.TestCase):
	def runTest():
		pass

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	tc = MyTestCase()
	print "now I'll use assertFalse to raise AssertionError\n"
	tc.assertFalse(True, 'expression is not false')
except AssertionError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python AssertError2.py
now I'll use assertFalse to raise AssertionError

catch <type 'exceptions.AssertionError'>
err obj args: ('expression is not false',)
the following is traceback:
  File "AssertError2.py", line 18, in <module>
    tc.assertFalse(True, 'expression is not false')
  File "/usr/local/lib/python2.7/unittest/case.py", line 416, in assertFalse
    raise self.failureException(msg)
[28763 refs]
[email protected]:~/test/except$ 


AttributeError:

    当属性成员不存在时,对其进行引用,就会抛出AttributeError异常,例如下面这个脚本:

# AttributeError.py 
import traceback
import sys

class MyCls(object):
	pass

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	myobj = MyCls()
	myobj.setcolor
except AttributeError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python AttributeError.py 
catch <type 'exceptions.AttributeError'>
err obj args: ("'MyCls' object has no attribute 'setcolor'",)
the following is traceback:
  File "AttributeError.py", line 15, in <module>
    myobj.setcolor
[18386 refs]
[email protected]:~/test/except$ 


    如果属性设置失败,例如对只读的属性进行设置时,也会抛出AttributeError异常:

# AttributeError2.py 
import traceback
import sys

class MyColor(object):
	def __init__(self):
		self._color = 'Init color'
	def getcolor(self):
		return self._color
	def setcolor(self, arg):
		self._color = arg
	writable_color = property(getcolor, setcolor)
	readonly_color = property(getcolor)

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	myobj = MyColor()
	print myobj.writable_color
	myobj.writable_color = 'set color by writable_color'
	print myobj.readonly_color
	print 'try to set new color use readonly_color\n'
	myobj.readonly_color = 'New color'
except AttributeError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    在MyColor的类定义中,由于writable_color属性既定义了getcolor作为属性获取函数,又定义了setcolor作为属性的设置函数。因此,writable_color是既可读又可写的属性。而readonly_color只设置了getcolor作为属性获取函数,并没有设置具体的属性设置函数。因此,readonly_color是只读属性,如果对该属性进行设置操作,就会抛出AttributeError异常。该脚本的执行结果如下:

[email protected]:~/test/except$ python AttributeError2.py 
Init color
set color by writable_color
try to set new color use readonly_color

catch <type 'exceptions.AttributeError'>
err obj args: ("can't set attribute",)
the following is traceback:
  File "AttributeError2.py", line 26, in <module>
    myobj.readonly_color = 'New color'
[18493 refs]
[email protected]:~/test/except$ 


EnvironmentError:

    一些和操作系统相关的底层错误,如OSError,以及I/O操作可能产生的IOError都继承自EnvironmentError:

EnvironmentError
      |    +-- IOError
      |    +-- OSError
      |         +-- WindowsError (Windows)
      |         +-- VMSError (VMS)


    例如下面这个脚本:

# EnvironmentError.py
try:
	print "open('not_exists'):"
	open('not_exists')
except EnvironmentError as myerrobj:
	print 'catch', type(myerrobj), 'use EnvironmentError'

print
try:
	import os
	print "os.mkdir('/root/testdir'):"
	os.mkdir('/root/testdir')
except EnvironmentError as myerrobj:
	print 'catch', type(myerrobj), 'use EnvironmentError'


    脚本的执行结果如下:

[email protected]:~/test/except$ python EnvironmentError.py 
open('not_exists'):
catch <type 'exceptions.IOError'> use EnvironmentError

os.mkdir('/root/testdir'):
catch <type 'exceptions.OSError'> use EnvironmentError
[18363 refs]
[email protected]:~/test/except$ 


IOError:

    当执行I/O操作时,如果发生错误,就会抛出IOError异常,例如磁盘满了,文件不存在等:

# IOError.py 
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	open('not_exists')
except IOError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj errno:', myerrobj.errno
	print 'err obj strerror:', myerrobj.strerror
	print 'err obj filename:', myerrobj.filename
	print dir(myerrobj)
	print 'the following is traceback:'
	view_traceback()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python IOError.py 
catch <type 'exceptions.IOError'>
err obj args: (2, 'No such file or directory')
err obj errno: 2
err obj strerror: No such file or directory
err obj filename: not_exists
[.........................................
 'args', 'errno', 'filename', 'message', 'strerror']
the following is traceback:
  File "IOError.py", line 11, in <module>
    open('not_exists')
[18368 refs]
[email protected]:~/test/except$ 


    可以看到,IOError的异常对象实例中,还多了errno(错误号),strerror(错误号对应的具体的错误信息),filename(I/O操作的文件名)这三个属性成员。可以使用dir内建函数来查看异常的对象实例中,到底多了哪些成员。

    其实,所有继承自EnvironmentError的子类,如IOError,OSError等,都会包含errno,strerror以及filename这三个属性成员。可以在Objects/exceptions.c文件中查看到相关的C代码:

static PyMemberDef EnvironmentError_members[] = {
    {"errno", T_OBJECT, offsetof(PyEnvironmentErrorObject, myerrno), 0,
        PyDoc_STR("exception errno")},
    {"strerror", T_OBJECT, offsetof(PyEnvironmentErrorObject, strerror), 0,
        PyDoc_STR("exception strerror")},
    {"filename", T_OBJECT, offsetof(PyEnvironmentErrorObject, filename), 0,
        PyDoc_STR("exception filename")},
    {NULL}  /* Sentinel */
};


OSError与WindowsError:

    主要用于os模块,当使用os模块执行一些和系统底层相关的操作时,如果发生错误,就会抛出OSError异常。例如下面这个脚本:

# OSError.py 
import traceback
import sys
import ctypes

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	import os
	os.mkdir('nonexists/testdir')
except OSError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj errno:', myerrobj.errno
	print 'err obj strerror:', myerrobj.strerror
	if(hasattr(myerrobj, 'winerror')):
		print 'err obj winerror:', myerrobj.winerror
		print 'winerror to error str:', ctypes.FormatError(myerrobj.winerror)
	print 'err obj filename:', myerrobj.filename
	print 'the following is traceback:'
	view_traceback()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python OSError.py 
catch <type 'exceptions.OSError'>
err obj args: (2, 'No such file or directory')
err obj errno: 2
err obj strerror: No such file or directory
err obj filename: nonexists/testdir
the following is traceback:
  File "OSError.py", line 13, in <module>
    os.mkdir('nonexists/testdir')
[21222 refs]
[email protected]:~/test/except$ 


    与之前的IOError一样,OSError也存在errno,strerror,filename属性,这是因为OSError也是EnvironmentError的子类的缘故。

    如果是在windows系统中执行这个脚本,那么捕获到的将会是WindowsError异常(WindowsError属于OSError的子类),在WindowsError异常的对象实例中,还会多出一个winerror属性,我们可以在Objects/exceptions.c文件中,查看到与WindowsError异常对象的属性成员相关的C代码:

static PyMemberDef WindowsError_members[] = {
    {"errno", T_OBJECT, offsetof(PyWindowsErrorObject, myerrno), 0,
        PyDoc_STR("POSIX exception code")},
    {"strerror", T_OBJECT, offsetof(PyWindowsErrorObject, strerror), 0,
        PyDoc_STR("exception strerror")},
    {"filename", T_OBJECT, offsetof(PyWindowsErrorObject, filename), 0,
        PyDoc_STR("exception filename")},
    {"winerror", T_OBJECT, offsetof(PyWindowsErrorObject, winerror), 0,
        PyDoc_STR("Win32 exception code")},
    {NULL}  /* Sentinel */
};


    OSError.py脚本在windows系统中的执行情况如下:

G:\Python27\mytest\except>..\..\python.exe OSError.py
catch <type 'exceptions.WindowsError'>
err obj args: (3, '')
err obj errno: 2
err obj strerror:
err obj winerror: 3
winerror to error str: 系统找不到指定的路径。
err obj filename: nonexists/testdir
the following is traceback:
  File "OSError.py", line 13, in <module>
    os.mkdir('nonexists/testdir')

G:\Python27\mytest\except>


    在windows中,需要以winerror的错误号为主。要得到winerror错误号所对应的具体的错误信息,可以使用ctypes.FormatError方法,脚本中就使用该方法得到了"系统找不到指定的路径。"的错误信息。

EOFError:

    该异常主要用于input()或者是raw_input()之类的内建函数。当raw_input试图从标准输入中读取数据时,如果直接遇到EOF(表示没有任何数据可供读取了)时,就会抛出EOFError异常。

    例如下面这个脚本:

# EOFError.py 
import traceback
import sys
import os

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

(r,w)=os.pipe()
pid = os.fork()
if(pid):
	os.close(r)
	os.write(w,'hello black...')
	os.close(w)
	os.waitpid(pid, 0)
else:
	os.close(w)
	ro = os.fdopen(r, 'r')
	sys.stdin = ro
	try:
		while(True):
			getinput = raw_input()
			print 'get input:', getinput
	except EOFError as myerrobj:
		print 'catch', type(myerrobj)
		print 'err obj args:', myerrobj.args
		print 'the following is traceback:'
		view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python EOFError.py 
get input: hello black...
catch <type 'exceptions.EOFError'>
err obj args: ('EOF when reading a line',)
the following is traceback:
  File "EOFError.py", line 24, in <module>
    getinput = raw_input()
[18363 refs]
[18363 refs]
[email protected]:~/test/except$ 


    父进程先创建了一个管道,并将管道的读端设置为子进程的标准输入设备。这样,父进程将'hello black...'写入管道后,子进程就可以从管道的读端以标准输入的方式,将数据给读取出来。父进程在将数据写入管道后,就将管道的写入端给close关闭了,这样,子进程再通过raw_input试图从管道中读取数据时,由于管道的写入端已经关闭了,raw_input通过底层的getc库函数去执行读操作时,getc就会返回-1(EOF即end-of-file),这样,raw_input就会抛出EOFError的异常了。

    以下是gdb的调试过程:

[email protected]:~/test/except$ gdb -q python
Reading symbols from /usr/local/bin/python...done.
(gdb) set follow-fork-mode child 
(gdb) set args EOFError.py 
(gdb) b builtin_raw_input 
Breakpoint 1 at 0x80eb077: file Python/bltinmodule.c, line 2022.
(gdb) r
Starting program: /usr/local/bin/python EOFError.py 
.....................................................

Breakpoint 1, builtin_raw_input (self=0x0, args=0xb7dcc034) at Python/bltinmodule.c:2022
2022	    PyObject *v = NULL;
(gdb) c
Continuing.
get input: hello black...

Breakpoint 1, builtin_raw_input (self=0x0, args=0xb7dcc034) at Python/bltinmodule.c:2022
2022	    PyObject *v = NULL;
(gdb) b PyFile_GetLine 
Breakpoint 2 at 0x807422a: file Objects/fileobject.c, line 1544.
(gdb) c
Continuing.

Breakpoint 2, PyFile_GetLine (f=0xb7d0e568, n=-1) at Objects/fileobject.c:1544
1544	    if (f == NULL) {
.....................................................
(gdb) n
1560	        result = get_line(fo, n);
(gdb) s
get_line (f=0xb7d0e568, n=-1) at Objects/fileobject.c:1409
1409	    FILE *fp = f->f_fp;
.....................................................
(gdb) n
1479	        while ((c = GETC(fp)) != EOF &&
(gdb) n
1483	        FUNLOCKFILE(fp);
(gdb) p c
$1 = -1
.....................................................
(gdb) n
PyFile_GetLine (f=0xb7d0e568, n=-1) at Objects/fileobject.c:1549
1549	    if (PyFile_Check(f)) {
(gdb) n
1589	    if (n < 0 && result != NULL && PyString_Check(result)) {
.....................................................
(gdb) l
1590	        char *s = PyString_AS_STRING(result);
1591	        Py_ssize_t len = PyString_GET_SIZE(result);
1592	        if (len == 0) {
1593	            Py_DECREF(result);
1594	            result = NULL;
1595	            PyErr_SetString(PyExc_EOFError,
1596	                            "EOF when reading a line");
1597	        }
1598	        else if (s[len-1] == '\n') {
1599	            if (result->ob_refcnt == 1) {
(gdb) c
Continuing.
catch <type 'exceptions.EOFError'>
err obj args: ('EOF when reading a line',)
err obj message: EOF when reading a line
the following is traceback:
  File "EOFError.py", line 18, in <module>
    getinput = raw_input()
[18364 refs]

Program exited normally.
(gdb) [18363 refs]
q
[email protected]:~/test/except$ 


    第一次执行builtin_raw_input时,读取到了管道的数据。第二次执行builtin_raw_input时,在get_line中,通过GETC(也就是getc库函数),直接返回的就是-1,-1就是EOF,最后就在PyFile_GetLine函数里,由于get_line没有获取到任何数据,就通过PyErr_SetString(PyExc_EOFError,"EOF when reading a line")来抛出了EOFError异常,并且设置了"EOF when reading a line"的异常信息。

    可以不需要在脚本中,手动创建管道来测试EOFError异常,因为命令行本身就提供了管道机制。例如下面这个脚本:

# EOFError2.py 
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	while(True):
		getinput = raw_input()
		print 'get input:', getinput
except EOFError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    直接使用命令行提供的管道机制,可以得到如下结果:

[email protected]:~/test/except$ echo 'hello black world' | python EOFError2.py
get input: hello black world
catch <type 'exceptions.EOFError'>
err obj args: ('EOF when reading a line',)
the following is traceback:
  File "EOFError2.py", line 12, in <module>
    getinput = raw_input()
[18363 refs]
[email protected]:~/test/except$ 


    系统会自动在echo与python进程之间建立一个管道。echo进程会从管道的写入端写入'hello black world',python EOFError2.py对应的进程则从管道的读端以标准输入的方式将数据读取出来。echo在写入数据后,会将管道的写入端给关闭掉,这样,当EOFError2.py所在的进程第二次通过raw_input尝试读管道时,就会因为直接读到了EOF,而抛出EOFError异常了。

ImportError:

    ImportError:一般用于,当import找不到指定的模块而导入失败时,或者from ... import语句无法导入指定的名称时,就会抛出ImportError异常。

    例如下面这个脚本:

# ImportError.py 
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	import module_not_exists
except ImportError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()

print

try:
	from sys import not_exists_name
except ImportError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python ImportError.py 
catch <type 'exceptions.ImportError'>
err obj args: ('No module named module_not_exists',)
the following is traceback:
  File "ImportError.py", line 11, in <module>
    import module_not_exists

catch <type 'exceptions.ImportError'>
err obj args: ('cannot import name not_exists_name',)
the following is traceback:
  File "ImportError.py", line 21, in <module>
    from sys import not_exists_name
[18364 refs]
[email protected]:~/test/except$ 


LookupError:

    IndexError与KeyError,都继承自LookupError:

LookupError
      |    +-- IndexError
      |    +-- KeyError


    因此,我们可以使用LookupError来捕获IndexError或者KeyError的异常对象实例。例如下面这个脚本:

# LookupError.py
import traceback
import sys

try:
	dct = {'hello':123, 'world':456}
	print "dct['hello']:", dct['hello']
	print "dct['black']:", dct['black']
except LookupError as myerrobj:
	print 'catch', type(myerrobj), 'use LookupError'

print 
try:
	lst = ['hello', 123, 'world', 456]
	print 'lst[0]:', lst[0]
	print 'lst[4]:', lst[4]
except LookupError as myerrobj:
	print 'catch', type(myerrobj), 'use LookupError'


    脚本的执行结果如下:

[email protected]:~/test/except$ python LookupError.py 
dct['hello']: 123
dct['black']: catch <type 'exceptions.KeyError'> use LookupError

lst[0]: hello
lst[4]: catch <type 'exceptions.IndexError'> use LookupError
[18363 refs]
[email protected]:~/test/except$ 


    此外,codecs.lookup()方法也可以直接抛出该异常。例如下面这个脚本:

# LookupError2.py
import traceback
import sys
import codecs

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	bs64_info = codecs.lookup('base64_codec')
	zlib_info = codecs.lookup('zlib_codec')
	print bs64_info
	print zlib_info
	print
	black_unknown = codecs.lookup('black_unknown')
except LookupError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ ls /usr/local/lib/python2.7/encodings/
...................................................................
base64_codec.py   cp850.py	    gb2312.py		 iso8859_6.py	   raw_unicode_escape.py
base64_codec.pyc  cp850.pyc	    gb2312.pyc		 iso8859_6.pyc	   raw_unicode_escape.pyc
base64_codec.pyo  cp850.pyo	    gb2312.pyo		 iso8859_6.pyo	   raw_unicode_escape.pyo
...................................................................
cp500.py	  euc_jisx0213.py   iso8859_2.py	 palmos.py	   zlib_codec.py
cp500.pyc	  euc_jisx0213.pyc  iso8859_2.pyc	 palmos.pyc	   zlib_codec.pyc
cp500.pyo	  euc_jisx0213.pyo  iso8859_2.pyo	 palmos.pyo	   zlib_codec.pyo
[email protected]:~/test/except$ python LookupError2.py 
<codecs.CodecInfo object for encoding base64 at 0xb75d0a54>
<codecs.CodecInfo object for encoding zlib at 0xb75d0ae4>

catch <type 'exceptions.LookupError'>
err obj args: ('unknown encoding: black_unknown',)
the following is traceback:
  File "LookupError2.py", line 17, in <module>
    black_unknown = codecs.lookup('black_unknown')
[19694 refs]
[email protected]:~/test/except$ 


    由于在作者的/usr/local/lib/python2.7/encodings目录中,并不存在black_unknown编码,因此,当使用codecs.lookup查询该编码时,就抛出了LookupError的异常。

IndexError:

    IndexError:当设置的索引超出了集合对象(如列表)的有效成员的范围时,就会抛出该异常。以下是一段与该异常相关的C代码(定义在Objects/listobject.c文件中):

static int
list_ass_item(PyListObject *a, Py_ssize_t i, PyObject *v)
{
    PyObject *old_value;
    if (i < 0 || i >= Py_SIZE(a)) {
        // 当对列表的成员,进行设置操作时,
        // 如果要设置的索引值小于0,
	// 或者大于等于列表的尺寸大小时,
	// 就会抛出IndexError异常,并产生
	// "list assignment index out of range"的错误信息。
        PyErr_SetString(PyExc_IndexError,
                        "list assignment index out of range");
        return -1;
    }
    if (v == NULL)
        return list_ass_slice(a, i, i+1, v);
    Py_INCREF(v);
    old_value = a->ob_item[i];
    a->ob_item[i] = v;
    Py_DECREF(old_value);
    return 0;
}


    例如下面这个脚本:

# IndexError.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	lst = [0, 1]
	lst[4] = 'test'
except IndexError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python IndexError.py 
catch <type 'exceptions.IndexError'>
err obj args: ('list assignment index out of range',)
the following is traceback:
  File "IndexError.py", line 12, in <module>
    lst[4] = 'test'
[18363 refs]
[email protected]:~/test/except$ 


KeyError:

    KeyError:主要用于具有key-value名值对的词典之类的对象的相关操作。以下是一段与该异常相关的C代码(定义在Objects/dictobject.c文件中):

static PyObject *
dict_popitem(PyDictObject *mp)
{
    Py_ssize_t i = 0;
    PyDictEntry *ep;
    PyObject *res;

    .........................................................
    res = PyTuple_New(2);
    if (res == NULL)
        return NULL;
    if (mp->ma_used == 0) {
        // 当词典中的有效成员数为0时,
	// 如果再对其执行popitem的操作,
	// 就会抛出KeyError异常,
	// 并设置"popitem(): dictionary is empty"的异常信息。
        Py_DECREF(res);
        PyErr_SetString(PyExc_KeyError,
                        "popitem(): dictionary is empty");
        return NULL;
    }
    .........................................................
}


    例如下面这个脚本:

# KeyError.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	dct = {'key1':'hello', 'key2':'black'}
	print dct.popitem()
	print dct.popitem()
	print dct.popitem() # will raise KeyError
except KeyError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python KeyError.py 
('key2', 'black')
('key1', 'hello')
catch <type 'exceptions.KeyError'>
err obj args: ('popitem(): dictionary is empty',)
the following is traceback:
  File "KeyError.py", line 14, in <module>
    print dct.popitem() # will raise KeyError
[18363 refs]
[email protected]:~/test/except$ 


MemoryError:

    当某项操作导致内存不足时,就会抛出MemoryError异常。发生该异常时,可以通过删除一些占用资源的对象,以释放内存,从而让程序能够正常运行下去。

    例如下面这个脚本:

# MemoryError.py
import traceback
import sys
import itertools

if(len(sys.argv) != 2 and len(sys.argv) != 3):
	sys.exit('usage: python ' + sys.argv[0] + ' <MBytes> [need_Discard=True]')

MBytes = int(sys.argv[1])
if(MBytes <= 0):
	sys.exit('MBytes must big then zero')
need_Discard = True
if(len(sys.argv) == 3):
	if(sys.argv[2] == 'False'):
		need_Discard = False
	elif(sys.argv[2] != 'True'):
		sys.exit('need_Discard must be True or False')

print "now I'm in MemoryError.py,", 'test:', sys.argv[1] + 'MBytes', 'need_Discard:', need_Discard
l = []
for i in range(3):
	print 
	try:
		for j in itertools.count(1):
			print 'i:%d' % i, 'j:%d' % j
			l.append('*' * (MBytes * 2**20))
	except MemoryError as myerrobj:
		print 'catch', type(myerrobj)
		if(len(l) > 0 and need_Discard == True):
			print 'discarding existing list'
			l = []


    脚本的执行结果如下:

[email protected]:~/test/except$ python MemoryError.py   
usage: python MemoryError.py <MBytes> [need_Discard=True]
[18945 refs]
[email protected]:~/test/except$ python MemoryError.py 512
now I'm in MemoryError.py, test: 512MBytes need_Discard: True

i:0 j:1
i:0 j:2
catch <type 'exceptions.MemoryError'>
discarding existing list

i:1 j:1
i:1 j:2
catch <type 'exceptions.MemoryError'>
discarding existing list

i:2 j:1
i:2 j:2
catch <type 'exceptions.MemoryError'>
discarding existing list
[18925 refs]
[email protected]:~/test/except$ 


    当第二次试图分配512M字节时,就因为内存不足而抛出了MemoryError异常。在该异常的处理代码中,通过丢弃掉占用内存的列表,从而让程序能够继续分配到新的内存资源。如果我们将脚本的第二个参数设置为False,也就是在发生异常时,不丢弃占用资源的列表的话,程序的后续操作就都没法分配到新的内存资源了:

[email protected]:~/test/except$ python MemoryError.py 512 False
now I'm in MemoryError.py, test: 512MBytes need_Discard: False

i:0 j:1
i:0 j:2
catch <type 'exceptions.MemoryError'>

i:1 j:1
catch <type 'exceptions.MemoryError'>

i:2 j:1
catch <type 'exceptions.MemoryError'>
[18925 refs]
[email protected]:~/test/except$ 


    当第一次发生MemoryError异常时,由于没有去丢弃列表。因此,后面两次试图分配内存资源时,在j为1时,就抛出了MemoryError的异常,也就是后续的操作都没法分配到新的内存。读者可以根据自己的情况调整每次需要分配的内存大小(以兆字节为基本单位)。

NameError与UnboundLocalError:

    如果在局部命名空间,全局命名空间,或者__builtins__内建模块中,都找不到指定的名称的话,就会抛出NameError或者UnboundLocalError的异常。

    例如下面这个脚本:

# Name_Unbound_Error.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

test = 'hello'
def mytest():
	test2 = 'world'
	print 'locals:', locals()
	print
	print 'globals:', globals()
	print
	print '__builtins__:', dir(__builtins__)
	print
	print 'co.co_names:', mytest.func_code.co_names
	print
	print 'co.co_varnames:', mytest.func_code.co_varnames
	print
	try:
		print traceback
		print sys
		print test
		print test2
		print test3
	except NameError as myerrobj:
		print 'catch', type(myerrobj)
		print 'err obj args:', myerrobj.args
		print 'the following is traceback:'
		view_traceback()

	print

	try:
		test4 = test4 + 2
	except UnboundLocalError as myerrobj:
		print 'catch', type(myerrobj)
		print 'err obj args:', myerrobj.args
		print 'the following is traceback:'
		view_traceback()

	print

	try:
		test5 = test5 + 2
	except NameError as myerrobj:
		print 'catch', type(myerrobj), 'use NameError'
		print 'err obj args:', myerrobj.args
		print 'the following is traceback:'
		view_traceback()

mytest()


    脚本的执行结果如下:

[email protected]:~/test/except$ python Name_Unbound_Error.py 
locals: {'test2': 'world'}

globals: {'__builtins__': <module '__builtin__' (built-in)>, 
 '__file__': 'Name_Unbound_Error.py', 
 'traceback': <module 'traceback' from '/usr/local/lib/python2.7/traceback.pyc'>, 
 'view_traceback': <function view_traceback at 0xb7460264>, '__package__': None, 
 'sys': <module 'sys' (built-in)>, 'mytest': <function mytest at 0xb7460764>, 
 'test': 'hello', '__name__': '__main__', '__doc__': None}

__builtins__: ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 
 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 
 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 
 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 
 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 
 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 
 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 
 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 
 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', 
 '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 
 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 
 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 
 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 
 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 
 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 
 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 
 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 
 'vars', 'xrange', 'zip']

co.co_names: ('locals', 'globals', 'dir', '__builtins__', 'mytest', 'func_code', 'co_names', 'co_varnames', 
 'traceback', 'sys', 'test', 'test3', 'NameError', 'type', 'args', 'view_traceback', 'UnboundLocalError')

co.co_varnames: ('test2', 'myerrobj', 'test4', 'test5')

<module 'traceback' from '/usr/local/lib/python2.7/traceback.pyc'>
<module 'sys' (built-in)>
hello
world
catch <type 'exceptions.NameError'>
err obj args: ("global name 'test3' is not defined",)
the following is traceback:
  File "Name_Unbound_Error.py", line 28, in mytest
    print test3

catch <type 'exceptions.UnboundLocalError'>
err obj args: ("local variable 'test4' referenced before assignment",)
the following is traceback:
  File "Name_Unbound_Error.py", line 38, in mytest
    test4 = test4 + 2

catch <type 'exceptions.UnboundLocalError'> use NameError
err obj args: ("local variable 'test5' referenced before assignment",)
the following is traceback:
  File "Name_Unbound_Error.py", line 48, in mytest
    test5 = test5 + 2
[18368 refs]
[email protected]:~/test/except$ 


    UnboundLocalError主要用于函数或方法中,Python会将函数中具有赋值操作的变量名假定为局部变量名,并将这些局部变量的名称都设置到函数的co.co_varnames元组中,如果在执行时,发现该元组中的名称不存在于locals局部命名空间中的话,就说明这个局部变量名没有绑定具体的对象就拿来使用了,python就会抛出UnboundLocalError异常。例如: 上面的test4存在于co_varnames元组中,却不存在于locals中,就抛出了UnboundLocalError异常。

    函数中,没被假定为局部变量的名称则放置在函数的co.co_names元组中,如果在执行时,发现该元组中的名称不存在于locals,globals或者__builtins__中的话,就会抛出NameError。例如:test3存在于co.co_names中,却不存在于locals,globals以及__builtins__中,因此,就抛出了NameError。

    当然,对于co.co_names中的名称,有的是属于特定对象里的成员的名称,如上面的func_code是属于mytest函数对象里的属性成员,因此,执行时,会在mytest对象中进行查询。如果查询不到,也会抛出异常,只不过此时抛出的会是AttributeError。

    co.co_names与co.co_varnames元组中的名称,应该是在词法扫描或者语法分析阶段设置好的。在具体执行时,还需要通过locals或者globals中的key-value名值对,映射到具体的对象,才能执行其他的操作。例如:test2在locals中会被映射为'world'字符串,然后才能使用'world'去执行各种操作。如果co.co_names与co.co_varnames元组中的名称不存在于locals或者globals或者__builtins__中的话,Python就没办法知道该名称具体要映射为哪个对象,也就没办法去执行具体的操作了,也就会抛出异常。上面分析表明,一般假定的局部变量会抛出UnboundLocalError,其他的则一般会抛出NameError。

    UnboundLocalError是NameError的子类,因此,可以用NameError来捕获到UnboundLocalError的异常对象。

ReferenceError:

    当weakref.proxy所创建的proxy对象所对应的referent被gc回收掉了时,再使用proxy对象访问referent中的属性时就会抛出ReferenceError。

    weakref.proxy在内部会为referent(原对象)创建一个weak reference(弱引用),因此,要理解ReferenceError,首先就需要知道weak reference的作用。

    一般情况下,当Python的某个对象被设置到一个新的变量中后,或者将该对象设置到某个列表中,或者某个词典中时,都会增加该对象内部的引用计数器的值。一个对象的引用计数器,是gc(垃圾收集器)用于判断该对象是否正在被使用中的参考依据,当引用计数器的值减为0时,gc就知道该对象没被使用了,就会将其回收掉。

    当我们为某个对象创建一个weak reference后,我们既可以使用weak reference来访问原对象(referent)中的属性,又不会增加该对象内部的引用计数器的值,因此,就不会影响gc对该对象的回收操作。根据weak reference(弱引用)的这一特性,就可以用于解决Circular References(循环引用)可能导致的内存泄露问题。循环引用的例子在后面会看到。

    我们先通过gdb调试器来看下,将一个对象设置到一个新的变量中后(也就是将该对象与新的变量名在命名空间中建立一个映射关系),该对象的引用计数器的变化情况:

[email protected]:~/test/except$ gdb -q python
Reading symbols from /usr/local/bin/python...done.
(gdb) r
Starting program: /usr/local/bin/python 
[Thread debugging using libthread_db enabled]
Python 2.7.8 (default, Jan  1 2016, 17:52:25) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class MyCls():
...   pass
... 
[40790 refs]
>>> myobj = MyCls() // 创建一个新的MyCls对象实例,并将其赋值给myobj变量。
[40796 refs]
>>> id(myobj) // 可以通过id(myobj)来查看到myobj所对应的对象,在内存中的指针值。
3083741372L
[40798 refs]
>>> hex(id(myobj)) // 通过hex将指针值转为十六进制格式。
'0xb7ce28bcL'
[40798 refs]
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) p *((PyObject*)0xb7ce28bc) // 下面的ob_refcnt就是对象内部的引用计数器。
// 一开始,对象的ob_refcnt(引用计数器)的值为1,因为上面将该对象与myobj变量名在命令空间中建立了映射关系。
$1 = {_ob_next = 0xb7ce0674, _ob_prev = 0xb7ce10a8, ob_refcnt = 1, 
  ob_type = 0x81bea40}
(gdb) c
Continuing.

[40798 refs]
>>> myobj2 = myobj // 将myobj所对应的对象,与myobj2变量名也建立一个映射关系。
[40802 refs]
>>> locals()
{'MyCls': <class __main__.MyCls at 0xb7d49c4c>, '__builtins__': <module '__builtin__' (built-in)>, 
 '__package__': None, 
 // 可以看到myobj与myobj2变量名,在命名空间中,都与指针值为0xb7ce28bc的对象建立了映射关系。
 'myobj2': <__main__.MyCls instance at 0xb7ce28bc>, 
 'myobj': <__main__.MyCls instance at 0xb7ce28bc>, 
 '__name__': '__main__', '__doc__': None}
[40808 refs]
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) p *((PyObject*)0xb7ce28bc)
$2 = {_ob_next = 0xb7ce0674, _ob_prev = 0xb7ce2958, ob_refcnt = 2, // myobj2 = myobj 执行后,对象的引用数变为了2
  ob_type = 0x81bea40}
(gdb) c
Continuing.

[40808 refs]
>>> myobj2 = None // 通过myobj2 = None,取消myobj2与原对象之间的映射关系。
[40808 refs]
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) p *((PyObject*)0xb7ce28bc)
$3 = {_ob_next = 0xb7ce0674, _ob_prev = 0xb7ce2958, ob_refcnt = 1, // myobj2 = None执行后, refcnt又变回1。
  ob_type = 0x81bea40}
(gdb) c
Continuing.

[40808 refs]
>>> myobj = None // myobj = None执行后,refcnt变为0,而被gc回收掉。
[40806 refs]
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) p *((PyObject*)0xb7ce28bc) // 可以看到,原对象的内存空间因为被gc回收掉了,所以下面就是一堆乱码了。
$4 = {_ob_next = 0xdbdbdbdb, _ob_prev = 0xdbdbdbdb, ob_refcnt = -606348325, 
  ob_type = 0xdbdbdbdb}
(gdb) c
Continuing.

[40806 refs]
>>> 


    可以看到,每次将对象赋值给一个新的变量名时,都会增加对象内部的引用计数器的值。当变量名取消了与原对象之间的映射关系后,就会将原对象的引用计数器的值减一,当引用计数器的值减为0时,才会被gc回收掉。

    我们再来看下,创建弱引用后,对象的引用计数器的实际情况:

>>> import weakref // 先导入weakref模块,下面的weakref.ref方法就需要该模块。
[42190 refs]
>>> myobj = MyCls() // 创建MyCls类的对象实例,并将其设置到myobj变量。
[42190 refs]
>>> hex(id(myobj))
'0xb7ce299cL'
[42190 refs]
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) p *((PyObject*)0xb7ce299c) // 一开始对象的ob_refcnt(引用计数器)的值为1
$7 = {_ob_next = 0xb7ce0cb4, _ob_prev = 0xb7ce10a8, ob_refcnt = 1, 
  ob_type = 0x81bea40}
(gdb) c
Continuing.

[42190 refs]
>>> r = weakref.ref(myobj) // 通过weakref.ref方法为myobj对象创建一个弱引用。
[42190 refs]
>>> hex(id(r)) // 弱引用的内存指针为0xb7ce1134,下面会根据该指针来查看弱引用的内部结构。
'0xb7ce1134L'
[42190 refs]
>>> hex(id(myobj)) // myobj对象的内存指针为0xb7ce299c
'0xb7ce299cL'
[42190 refs]
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) p *((PyWeakReference*)0xb7ce1134)
$8 = {_ob_next = 0xb7ce299c, _ob_prev = 0xb7ce10a8, ob_refcnt = 1, 
  // 弱引用内部的wr_object的指针值,就等于myobj对象的指针值。
  // 之所以可以通过弱引用来访问原对象中的属性(原对象一般被称作referent),
  // 就是通过wr_object来实现的。
  ob_type = 0x81cc700, wr_object = 0xb7ce299c, 
  wr_callback = 0x0, hash = -1, wr_prev = 0x0, wr_next = 0x0}
(gdb) p *((PyObject*)0xb7ce299c)
  // 可以看到,为myobj对象创建弱引用后,该对象的ob_refcnt(引用计数器)的值还是1.
$9 = {_ob_next = 0xb7ce0cb4, _ob_prev = 0xb7ce1134, ob_refcnt = 1, 
  ob_type = 0x81bea40}
(gdb) c
Continuing.

[42190 refs]
// myobj = None执行后,就取消了myobj变量名与原对象之间的映射关系,
// 这样,原对象中的引用计数器的值就会变为0,
// gc在回收该对象时,会向之前创建的弱引用发送内部通知,
// 弱引用在收到通知后,会将内部的wr_object指向None。
>>> myobj = None 
[42188 refs]
>>> 
Program received signal SIGINT, Interrupt.
0xb7ee09f8 in ___newselect_nocancel () from /lib/libc.so.6
(gdb) p *((PyObject*)0xb7ce299c) // 原对象被gc回收掉了,因此,下面就是一堆乱码了。
$10 = {_ob_next = 0xdbdbdbdb, _ob_prev = 0xdbdbdbdb, ob_refcnt = -606348325, 
  ob_type = 0xdbdbdbdb}
(gdb) p *((PyWeakReference*)0xb7ce1134) 
  // 当myobj = None执行后,gc会通知weak reference(弱引用),
  // 弱引用中的wr_object就会自动变为None的指针值(这里为0x81c69ac,可以通过下面的ob_type来查看具体的类型名)。
$11 = {_ob_next = 0xb7ce5e6c, _ob_prev = 0xb7ce10a8, ob_refcnt = 1, 
  ob_type = 0x81cc700, wr_object = 0x81c69ac, wr_callback = 0x0, hash = -1, 
  wr_prev = 0x0, wr_next = 0x0}
(gdb) p *(((PyWeakReference*)0xb7ce1134)->wr_object)->ob_type 
  // 可以看到wr_object的ob_type(对象类型)为NoneType
$12 = {_ob_next = 0xb7dd7f74, _ob_prev = 0xb7ddc504, ob_refcnt = 5, 
  ob_type = 0x81cb5a0, ob_size = 0, tp_name = 0x819b60b "NoneType", 
  ..........................................}
(gdb) c
Continuing.

[42188 refs]
>>> 


    可以看到,创建弱引用,不会对原对象的引用计数器的值产生任何影响。当原对象被gc回收掉后,弱引用内部的wr_object就会自动指向None。

    弱引用,或者proxy(弱引用代理)都可以访问原对象中的属性成员:

>>> import weakref
>>> class MyCls():
...   def __init__(self):
...     self.name = 'black'
... 
>>> myobj = MyCls()
>>> r = weakref.ref(myobj)
>>> myobj.name
'black'
>>> r().name
'black'
>>> proxy = weakref.proxy(myobj) // proxy会将属性请求,如name等,代理转发给原对象。
>>> proxy.name
'black'
>>> myobj = None
>>> proxy.name // myobj = None执行后,原对象被gc回收了,proxy无法代理转发属性请求,而抛出ReferenceError异常。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ReferenceError: weakly-referenced object no longer exists
>>> 


    proxy(弱引用代理)是弱引用的一种便捷版,它可以更加方便的访问原对象中的属性,不像弱引用那样,还需要先将原对象返回,再去访问原对象的属性,如上面的r().name,先通过r()返回原对象,再通过原对象访问name属性。而proxy则在内部直接将属性请求转发给原对象,因此,proxy可以像原对象那样直接访问属性,如上面的proxy.name。

    proxy在转发属性请求之前,会先通过内部的proxy_checkref这个C函数去检测它所引用的对象是否变为了None,如果变为了None,就说明原对象被gc回收掉了,就会抛出ReferenceError异常(下面的C代码定义在Objects/weakrefobject.c文件中):

static int
proxy_checkref(PyWeakReference *proxy)
{
    if (PyWeakref_GET_OBJECT(proxy) == Py_None) {
        PyErr_SetString(PyExc_ReferenceError,
                        "weakly-referenced object no longer exists");
        return 0;
    }
    return 1;
}


    利用weakreference可以解决Circular References(循环引用)可能导致的内存泄露问题。

    先来看个循环引用的例子:

# Circular_References.py

class A(object):
	def __init__(self, b_instance):
		self.b = b_instance
	def __del__(self):
		print 'die A'

class B(object):
	def __init__(self):
		self.a = A(self)
	def __del__(self):
		print "die B"

def test():
	b = B()

test()

import gc
gc.collect()
print 'gc.garbage:', gc.garbage


    脚本的执行结果如下:

[email protected]:~/test/except$ python Circular_References.py 
gc.garbage: [<__main__.B object at 0xb7463814>, <__main__.A object at 0xb74638bc>]
[18738 refs]
[email protected]:~/test/except$ 


    Circular_References.py脚本中,因为A,B相互引用(B的属性a对应为A对象,而A的属性b对应为B对象),就出现了循环引用,如下图所示:


图1

    Python对于循环引用中的对象,如果它们又存在__del__方法的话,gc就不会去回收它们。因此,A,B对应的对象就都无法被删除掉,也就出现了内存泄露(如果去掉A中的__del__方法的话,gc就可以回收A的对象,B也是同理)。

    对于这种情况,一种解决方法是,手动打破这种循环引用,然后就可以将它们给删除掉,例如下面这个脚本:

# Circular_References2.py
class A(object):
	def __init__(self, b_instance):
		self.b = b_instance
	def __del__(self):
		print 'die A'

class B(object):
	def __init__(self):
		self.a = A(self)
	def __del__(self):
		print "die B"

def test():
	b = B()

test()

import gc
gc.collect()
print 'gc.garbage:', gc.garbage
while gc.garbage:
	if gc.garbage[0].__class__.__name__ == 'B':
		gc.garbage[0].a = None
	del gc.garbage[0]
gc.collect()
print 'gc.garbage:', gc.garbage


    脚本的最后,通过将B对象中的属性a设置为None,从而打破了前面图1中所示的循环引用,这样就可以通过del关键字将它们给手动删除掉了。如果没有gc.garbage[0].a = None语句的话,循环引用就不会被打破,此时,即便强行使用del关键字也无法删除它们。脚本的执行结果如下:

[email protected]:~/test/except$ python Circular_References2.py 
gc.garbage: [<__main__.B object at 0xb751884c>, <__main__.A object at 0xb75188f4>]
die A
die B
gc.garbage: []
[18729 refs]
[email protected]:~/test/except$ 


    还有一种解决方法是:使用弱引用。因为,使用弱引用的话,就不会出现循环引用,例如下面这个脚本:

# WeakReference.py
import weakref

class A(object):
	def __init__(self, b_instance):
		self.b = b_instance
	def __del__(self):
		print 'die A'

class B(object):
	def __init__(self):
		self.a = A(weakref.proxy(self))
	def __del__(self):
		print "die B"

def test():
	b = B()

test()
print 'after test()'
import gc
gc.collect()
print 'gc.garbage:', gc.garbage


    脚本中,通过weakref.proxy创建了B的对象的弱引用,由于弱引用不会增加B的对象的引用计数器的值。因此,gc只会认为B对A存在引用,而A对B的弱引用不会被gc当成常规的引用关系,也就不会出现前面图1所示的循环引用的情况,这样,当test()函数执行结束后,gc就会把A和B的对象依次回收掉。脚本的执行结果如下:

[email protected]:~/test/except$ python WeakReference.py 
die B
die A
after test()
gc.garbage: []
[18879 refs]
[email protected]:~/test/except$ 


    弱引用还可以用于Caching Objects(缓存对象),具体的例子,请参考 https://pymotw.com/2/weakref/ 该链接对应的文章,该文章的底部就有缓存对象的例子。在该例子中用到的WeakValueDictionary词典,在添加成员时,添加的其实是目标对象的弱引用。因此,往该词典中添加对象时,不会增加目标对象的引用数。除非从词典中根据弱引用,将对象提取出来,并赋予到另外一个变量中。

RuntimeError与NotImplementedError:

    RuntimeError:当某个错误找不到具体的错误分类时,才会落入RuntimeError。一般解释器出现的情况比较少,多半是在用户脚本中手动通过raise关键字来抛出。

    NotImplementedError是RuntimeError的子类:

RuntimeError
      |    +-- NotImplementedError


    NotImplementedError:在用户脚本中,一般由base class(基类)的abstract methods(抽象方法)来抛出,抽象方法是指那些需要在子类中实现的方法。例如下面这个脚本:

# NotImplementedError.py 
import traceback
import sys

class BaseClass(object):
    """Defines the interface"""
    def __init__(self):
        super(BaseClass, self).__init__()
    def do_something(self):
        """The interface, not implemented"""
        raise NotImplementedError(self.__class__.__name__ + '.do_something should access through subclass')

class SubClass(BaseClass):
    """Implementes the interface"""
    def do_something(self):
        """really does something"""
        print self.__class__.__name__ + ' doing something!'

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	SubClass().do_something()
	BaseClass().do_something()
except NotImplementedError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行情况如下:

[email protected]:~/test/except$ python NotImplementedError.py 
SubClass doing something!
catch <type 'exceptions.NotImplementedError'>
err obj args: ('BaseClass.do_something should access through subclass',)
the following is traceback:
  File "NotImplementedError.py", line 26, in <module>
    BaseClass().do_something()
  File "NotImplementedError.py", line 11, in do_something
    raise NotImplementedError(self.__class__.__name__ + '.do_something should access through subclass')
[18496 refs]
[email protected]:~/test/except$ 


    BaseClass中的do_something属于抽象函数,该函数只是一个接口(在基类中并没有具体的实现代码),需要在子类中进行实现,并通过子类去方法。如果直接访问BaseClass中的do_something方法的话,就会手动通过raise去抛出NotImplementedError异常。

SyntaxError:

    一般在import导入其他模块时,或者eval,input动态解析代码时,或者是interactively(交互式输入代码)的环境下。如果有语法错误,就会抛出SyntaxError异常。

    例如下面这个脚本:

# SyntaxError.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

for i in range(2):
	print
	try:
		if i == 0:
			#exec('a = 5 / 3:')
			exec('a = 5 / 3')
			print 'pass exec'
		else:
			eval('a = 5 / 3')
			print 'pass eval'
	except SyntaxError as myerrobj:
		print 'catch', type(myerrobj)
		print 'err obj args:', myerrobj.args
		print 'err obj filename:', myerrobj.filename
		print 'err obj lineno:', myerrobj.lineno
		print 'err obj msg:', myerrobj.msg
		print 'err obj offset:', myerrobj.offset
		print 'err obj text:', myerrobj.text
		print 'the following is traceback:'
		view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python SyntaxError.py 

pass exec

catch <type 'exceptions.SyntaxError'>
err obj args: ('invalid syntax', ('<string>', 1, 3, 'a = 5 / 3'))
err obj filename: <string>
err obj lineno: 1
err obj msg: invalid syntax
err obj offset: 3
err obj text: a = 5 / 3
the following is traceback:
  File "SyntaxError.py", line 18, in <module>
    eval('a = 5 / 3')
[18363 refs]
[email protected]:~/test/except$ 


    可以看到SyntaxError的异常对象实例中,还多出了filename(语法错误所在的文件名),lineno(语法错误所在的行号),msg(具体的语法错误信息),offset(语法错误的偏移值),text(出错的代码)这几个属性。

    通过filename与lineno,我们就可以知道在哪个文件的第几行出现了语法错误。通过offset,就可以知道,在text(出错代码)中的具体错误位置,例如,上面的offset为3,表示在text中偏移值为3的位置处,也就是'='(赋值符号)所在的位置,出现了语法错误。因为,eval()只能计算表达式,而表达式中不能包含statement(语句)。因此,在eval中使用赋值语句就会产生语法错误。只有exec()可以执行语句。

    在Objects/exceptions.c文件中,可以看到与SyntaxError的属性成员的定义,相关的C代码:

static PyMemberDef SyntaxError_members[] = {
    {"msg", T_OBJECT, offsetof(PySyntaxErrorObject, msg), 0,
        PyDoc_STR("exception msg")},
    {"filename", T_OBJECT, offsetof(PySyntaxErrorObject, filename), 0,
        PyDoc_STR("exception filename")},
    {"lineno", T_OBJECT, offsetof(PySyntaxErrorObject, lineno), 0,
        PyDoc_STR("exception lineno")},
    {"offset", T_OBJECT, offsetof(PySyntaxErrorObject, offset), 0,
        PyDoc_STR("exception offset")},
    {"text", T_OBJECT, offsetof(PySyntaxErrorObject, text), 0,
        PyDoc_STR("exception text")},
    {"print_file_and_line", T_OBJECT,
        offsetof(PySyntaxErrorObject, print_file_and_line), 0,
        PyDoc_STR("exception print_file_and_line")},
    {NULL}  /* Sentinel */
};


IndentationError:

    IndentationError:由于缩进造成的语法错误。例如下面这个脚本:

# IndentationError.py
import traceback
import sys

code = \
'''a = 5
if(a == 5):
print 'a == 5'
else:
	print 'a != 5'
'''

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	exec(code)
except IndentationError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj filename:', myerrobj.filename
	print 'err obj lineno:', myerrobj.lineno
	print 'err obj msg:', myerrobj.msg
	print 'err obj offset:', myerrobj.offset
	print 'err obj text:', myerrobj.text
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python IndentationError.py 
catch <type 'exceptions.IndentationError'>
err obj args: ('expected an indented block', ('<string>', 3, 5, "print 'a == 5'\n"))
err obj filename: <string>
err obj lineno: 3
err obj msg: expected an indented block
err obj offset: 5
err obj text: print 'a == 5'

the following is traceback:
  File "IndentationError.py", line 19, in <module>
    exec(code)
[18363 refs]
[email protected]:~/test/except$ 


    由于IndentationError是SyntaxError的子类。因此,IndentationError的异常对象实例中,也会包含filename,lineno,msg,offset以及text这几个属性。这些属性的具体含义,可以参考之前的SyntaxError异常。上面例子中,由于if下面的print语句没有进行缩进,因此就抛出了IndentationError的异常。

TabError:

    如果在代码缩进中,混合使用Tab和空格方式的话(某些行使用的Tab来缩进,某些行则使用的空格符进行缩进),就会抛出TabError。不过在python 2.x中,还需要加入-tt选项,才会抛出该异常。

    例如下面这个脚本:

# TabError.py
import traceback
import sys

code = \
'''a = 5
if(a == 5):
	print 'a == 5'
        print 'hello'
else:
	print 'a != 5'
'''

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	exec(code)
except TabError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'err obj filename:', myerrobj.filename
	print 'err obj lineno:', myerrobj.lineno
	print 'err obj msg:', myerrobj.msg
	print 'err obj offset:', myerrobj.offset
	print 'err obj text:', myerrobj.text
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python TabError.py 
a == 5
hello
[18363 refs]
[email protected]:~/test/except$ python -tt TabError.py 
catch <type 'exceptions.TabError'>
err obj args: ('inconsistent use of tabs and spaces in indentation', ('<string>', 4, 22, "        print 'hello'\n"))
err obj filename: <string>
err obj lineno: 4
err obj msg: inconsistent use of tabs and spaces in indentation
err obj offset: 22
err obj text:         print 'hello'

the following is traceback:
  File "TabError.py", line 20, in <module>
    exec(code)
[18363 refs]
[email protected]:~/test/except$ 


    可以看到,对于python 2.x,只有加入-tt选项,才能检测出tab缩进与空格缩进的不一致,并根据这种不一致抛出TabError异常。

    在print 'a == 5'语句所在的行,用的是Tab缩进方式,即:[tab]print 'a == 5' ,这里用[tab]表示Tab制表符。

    而在print 'hello'所在的行,用的则是空格符的缩进方式,即:[space][space][space][space][space][space][space][space]print 'hello',这里用[space]来表示空格符。

SystemError:

    SystemError:一般是在Python解释器出现严重错误的时候,才会抛出该异常:

Python/ceval.c 1867:

        case LOAD_LOCALS:
            if ((x = f->f_locals) != NULL) {
                Py_INCREF(x);
                PUSH(x);
                continue;
            }
            PyErr_SetString(PyExc_SystemError, "no locals");
            break;


    例如在执行时,找不到locals(局部命名空间所对应的词典)等。一般是解释器的BUG或者操作系统错误引起的,发生的情况很少。

TypeError:

    TypeError:当某项操作无法应用到指定的对象类型上时,就会抛出该异常。例如下面这段C代码(定义在Objects/stringobject.c文件中):

static PyObject *
string_concat(register PyStringObject *a, register PyObject *bb)
{
    register Py_ssize_t size;
    register PyStringObject *op;
    if (!PyString_Check(bb)) {
#ifdef Py_USING_UNICODE
        if (PyUnicode_Check(bb))
            return PyUnicode_Concat((PyObject *)a, bb);
#endif
        if (PyByteArray_Check(bb))
            return PyByteArray_Concat((PyObject *)a, bb);
        PyErr_Format(PyExc_TypeError,
                     "cannot concatenate 'str' and '%.200s' objects",
                     Py_TYPE(bb)->tp_name);
        return NULL;
    }
    .........................
}


    当使用'+'运算符进行连接操作时,如果连接操作中,其中一个操作数是字符串的话。那么另一个操作数就必须是字符串对象,Unicode对象,或者是ByteArray(字节数组)。否则就会抛出TypeError异常。例如下面这个脚本:

# TypeError.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	'hello' + 123
except TypeError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    这个脚本的执行结果如下:

[email protected]:~/test/except$ python TypeError.py 
catch <type 'exceptions.TypeError'>
err obj args: ("cannot concatenate 'str' and 'int' objects",)
the following is traceback:
  File "TypeError.py", line 11, in <module>
    'hello' + 123
[18363 refs]
[email protected]:~/test/except$ 


ValueError以及UnicodeError:

    ValueError:当提供给函数的参数,不符号函数的要求时,一般就会抛出ValueError异常。例如下面这段C代码(定义在Objects/intobject.c文件中):

PyObject *
PyInt_FromString(char *s, char **pend, int base)
{
    char *end;
    long x;
    Py_ssize_t slen;
    PyObject *sobj, *srepr;

    // 在字符串转为整数时,如果提供的base参数不符合要求的话,就会抛出ValueError
    if ((base != 0 && base < 2) || base > 36) {
        PyErr_SetString(PyExc_ValueError,
                        "int() base must be >= 2 and <= 36");
        return NULL;
    }
    ..........................
}


    以下是测试脚本:

# ValueError.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	print "int('100100', base=2):", int('100100', base=2), '\n'
	print "int('0xFBF001', base=16)", int('0xFBF001', base=16), '\n'
	print "int('12345', base=38)", int('12345', 38)
except ValueError as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python ValueError.py 
int('100100', base=2): 36 

int('0xFBF001', base=16) 16510977 

int('12345', base=38) catch <type 'exceptions.ValueError'>
err obj args: ('int() base must be >= 2 and <= 36',)
the following is traceback:
  File "ValueError.py", line 13, in <module>
    print "int('12345', base=38)", int('12345', 38)
[18363 refs]
[email protected]:~/test/except$ 


    与Unicode对象相关的异常,都继承自ValueError:

ValueError
      +-- UnicodeError
                 +-- UnicodeDecodeError
                 +-- UnicodeEncodeError
                 +-- UnicodeTranslateError


    UnicodeError多了以下5个成员(下面的C代码定义在Objects/exceptions.c文件中):

static PyMemberDef UnicodeError_members[] = {
    {"encoding", T_OBJECT, offsetof(PyUnicodeErrorObject, encoding), 0,
        PyDoc_STR("exception encoding")},
    {"object", T_OBJECT, offsetof(PyUnicodeErrorObject, object), 0,
        PyDoc_STR("exception object")},
    {"start", T_PYSSIZET, offsetof(PyUnicodeErrorObject, start), 0,
        PyDoc_STR("exception start")},
    {"end", T_PYSSIZET, offsetof(PyUnicodeErrorObject, end), 0,
        PyDoc_STR("exception end")},
    {"reason", T_OBJECT, offsetof(PyUnicodeErrorObject, reason), 0,
        PyDoc_STR("exception reason")},
    {NULL}  /* Sentinel */
};


    下面是一个简单的测试脚本:

# UnicodeError.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	b = 'helloworld'.encode('zlib')
	print 'b:', repr(b)
	unicode(b)
except UnicodeError as myerrobj:
	print 'catch', type(myerrobj), 'use UnicodeError'
	print 'err obj args:', myerrobj.args
	print 'err obj encoding:', myerrobj.encoding
	print 'err obj object:', repr(myerrobj.object)
	print 'err obj start:', myerrobj.start
	print 'err obj end:', myerrobj.end
	print 'err obj reason:', myerrobj.reason
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/except$ python UnicodeError.py 
b: 'x\x9c\xcbH\xcd\xc9\xc9/\xcf/\xcaI\x01\x00\x176\x04='
catch <type 'exceptions.UnicodeDecodeError'> use UnicodeError
err obj args: ('ascii', 'x\x9c\xcbH\xcd\xc9\xc9/\xcf/\xcaI\x01\x00\x176\x04=', 1, 2, 'ordinal not in range(128)')
err obj encoding: ascii
err obj object: 'x\x9c\xcbH\xcd\xc9\xc9/\xcf/\xcaI\x01\x00\x176\x04='
err obj start: 1
err obj end: 2
err obj reason: ordinal not in range(128)
the following is traceback:
  File "UnicodeError.py", line 13, in <module>
    unicode(b)
[18751 refs]
[email protected]:~/test/except$ 


    'helloworld'经过zlib编码后,得到的字符串数据中,有的字符的编码超出了有效的ASCII码的范围。而unicode脚本函数在将对象b转换为Unicode对象时,最终会通过下面的PyUnicode_DecodeASCII的C函数,将b中的ASCII字符decode为对应的unicode字符,当遇到超出ASCII码范围的字符时,就会抛出UnicodeDecodeError异常:

Objects/unicodeobject.c 3785:

/* --- 7-bit ASCII Codec -------------------------------------------------- */

PyObject *PyUnicode_DecodeASCII(const char *s,
                                Py_ssize_t size,
                                const char *errors)
{
    ............................................
    while (s < e) {
        register unsigned char c = (unsigned char)*s;
        if (c < 128) {
	     // 如果字符的编码小于128,就是有效的ASCII码,
	     // 就直接设置到p所在的内存(p是Unicode字符串的16位的指针(short unsigned int *))。
            *p++ = c;
            ++s;
        }
        else {
             // 将超出ASCII范围的无效字符的起始字节偏移值记录到startinpos中,最终会成为异常对象的start成员。
            startinpos = s-starts;
	     // startinpos + 1得到endinpos,该值将成为异常对象的end成员。
            endinpos = startinpos + 1;
            outpos = p - (Py_UNICODE *)PyUnicode_AS_UNICODE(v);
            // 进入unicode_decode_call_errorhandler函数,去设置UnicodeDecodeError异常。
            if (unicode_decode_call_errorhandler(
                    errors, &errorHandler,
                    "ascii", "ordinal not in range(128)",
                    starts, size, &startinpos, &endinpos, &exc, &s,
                    &v, &outpos, &p))
                goto onError;
        }
    }
    ........................
}


Warning:

    Warning:就是一些可能的警告信息。例如,当某些函数具有安全隐患时,就会产生警告。当然,你可以自定义警告发生时,所需执行的动作。例如,默认动作就是直接将警告信息显示出来,但是程序还是可以继续执行。

    你也可以通过warnings模块的filters过滤器列表,将指定的警告给忽略掉,不让它出现任何显示,也可以让警告以异常的形式抛出来,当以异常的形式抛出来时,可以通过try...except来捕获它。

    例如在上一篇文章中,我们介绍过的os.tempnam方法,就存在安全隐患。使用该方法时,就会产生警告信息,以下是相关的C代码:

Modules/posixmodule.c 7501:

static PyObject *
posix_tempnam(PyObject *self, PyObject *args)
{
    PyObject *result = NULL;
    char *dir = NULL;
    char *pfx = NULL;
    char *name;

    if (!PyArg_ParseTuple(args, "|zz:tempnam", &dir, &pfx))
    return NULL;

    // 产生RuntimeWarning,来警告该函数存在安全隐患。
    if (PyErr_Warn(PyExc_RuntimeWarning,
                   "tempnam is a potential security risk to your program") < 0)
        return NULL;

    if (PyErr_WarnPy3k("tempnam has been removed in 3.x; "
                       "use the tempfile module", 1) < 0)
        return NULL;

#ifdef MS_WINDOWS
    name = _tempnam(dir, pfx);
#else
    name = tempnam(dir, pfx);
#endif
    if (name == NULL)
        return PyErr_NoMemory();
    result = PyString_FromString(name);
    free(name);
    return result;
}


    例如下面这个脚本:

# RuntimeWarning.py 
import os, warnings, re,sys

warnings.filters.insert(0, ('error', None, RuntimeWarning, None, 0))

try:
	tmpname = os.tempnam()
except RuntimeWarning as warnobj:
	print 'catch', type(warnobj)
	print 'warnobj.args:', warnobj.args

warnings.filters[:] = []
warnings.filters.insert(0, ('ignore', re.compile('^tempnam is a potential*', re.I), \
			RuntimeWarning, re.compile('__main__'), 0))

print
print __name__
tempnam = os.tempnam()
print 'tempnam:', tempnam

print
tmpnam = os.tmpnam()
print 'tmpnam:', tmpnam


    脚本的执行结果如下:

[email protected]:~/test/warnings$ python RuntimeWarning.py 
catch <type 'exceptions.RuntimeWarning'>
warnobj.args: ('tempnam is a potential security risk to your program',)

__main__
tempnam: /tmp/filewXpQOY

RuntimeWarning.py:22: RuntimeWarning: tmpnam is a potential security risk to your program
  tmpnam = os.tmpnam()
tmpnam: /tmp/filegtaGuN
[18367 refs]
[email protected]:~/test/warnings$ 


    我们通过warnings.filters,既可以让警告信息以异常的形式抛出来,也可以忽略掉特定的警告信息。

    warnings.filters其实是一个列表,在Python的内部,处理某个警告时,会先对该列表进行查询,然后确定需要执行的动作,相关的C代码如下:

Python/_warnings.c 103:

/* The item is a borrowed reference. */
static const char *
get_filter(PyObject *category, PyObject *text, Py_ssize_t lineno,
           PyObject *module, PyObject **item)
{
    PyObject *action;
    Py_ssize_t i;
    PyObject *warnings_filters;

    warnings_filters = get_warnings_attr("filters");
    if (warnings_filters == NULL) {
        if (PyErr_Occurred())
            return NULL;
    }
    else {
        Py_DECREF(_filters);
        _filters = warnings_filters;
    }

    // warnings模块中的filters过滤器必须是一个列表。
    if (!PyList_Check(_filters)) {
        PyErr_SetString(PyExc_ValueError,
                        MODULE_NAME ".filters must be a list");
        return NULL;
    }

    /* _filters could change while we are iterating over it. */
    // 通过循环,将当前产生的警告,与filters列表里的每个成员进行匹配,
    // 当发生匹配时,就将列表成员中指定的动作返回。
    for (i = 0; i < PyList_GET_SIZE(_filters); i++) {
        PyObject *tmp_item, *action, *msg, *cat, *mod, *ln_obj;
        Py_ssize_t ln;
        int is_subclass, good_msg, good_mod;

        tmp_item = *item = PyList_GET_ITEM(_filters, i);
        // filters中的每个成员都必须是一个包含有5个成员的元组。
        if (PyTuple_Size(tmp_item) != 5) {
            PyErr_Format(PyExc_ValueError,
                         MODULE_NAME ".filters item %zd isn't a 5-tuple", i);
            return NULL;
        }

        /* Python code: action, msg, cat, mod, ln = item */
        // 元组的第一项用于指定动作,可以是error,always,ignore,once,module,default,
        // 动作的具体含义后面会介绍。
        action = PyTuple_GET_ITEM(tmp_item, 0);
        // 第二项指定需要匹配的警告信息,需要是一个正则表达式对象,
        // 用于匹配哪些警告信息需要处理。如果为None,则任意信息都匹配。
        // 例如前面例子中的'^tempnam is a potential*'
        // 就表示只有以'tempnam is a potential'开头的警告信息才进行匹配。
        msg = PyTuple_GET_ITEM(tmp_item, 1);
        // 第三项指定需要匹配的警告类型,如RuntimeWarning, UserWarning等。
        cat = PyTuple_GET_ITEM(tmp_item, 2);
        // 第四项指定异常发生的模块,也是正则表达式,只有在匹配到的模块中,
        // 发生了该警告时,才进行指定的动作。如果为None,则任意模块都匹配。
        // 例如前面例子中的'__main__'就表示只有在主程序模块中发生警告时,才进行匹配。
        mod = PyTuple_GET_ITEM(tmp_item, 3);
        // 指定行号,在匹配的行产生警告时,才进行指定的动作。如果为0,则任意一行都匹配。
        ln_obj = PyTuple_GET_ITEM(tmp_item, 4);

        // 将列表成员中的msg与text(当前产生的警告的信息)进行正则匹配
        good_msg = check_matched(msg, text);
        // 将列表成员中的mod与module(当前警告产生时,所在的模块)进行正则匹配
        good_mod = check_matched(mod, module);
        // 将列表成员中的cat与category(当前产生的警告的类型)进行检测,
        // 如果category是cat的子类或者category与cat是相同的警告类型的话,则可以通过检测(也就是相匹配)。
        is_subclass = PyObject_IsSubclass(category, cat);
        ln = PyInt_AsSsize_t(ln_obj);
        if (good_msg == -1 || good_mod == -1 || is_subclass == -1 ||
            (ln == -1 && PyErr_Occurred()))
            return NULL;

        // 如果当前产生的警告的信息,所在的模块,警告类型,所在行,都与列表成员中定义的,
        // 相匹配的话,就将该列表成员中的action作为需要执行的动作返回。
        if (good_msg && is_subclass && good_mod && (ln == 0 || lineno == ln))
            return PyString_AsString(action);
    }

    // 如果没有在filters中找到匹配的列表成员的话,就返回默认动作。
    action = get_default_action();
    if (action != NULL) {
        return PyString_AsString(action);
    }

    PyErr_SetString(PyExc_ValueError,
                    MODULE_NAME ".defaultaction not found");
    return NULL;
}


    每个动作的具体含义,可以参考下面这段C代码:

Python/_warnings.c 289:

static PyObject *
warn_explicit(PyObject *category, PyObject *message,
              PyObject *filename, int lineno,
              PyObject *module, PyObject *registry, PyObject *sourceline)
{
    .....................................................

    action = get_filter(category, text, lineno, module, &item);
    if (action == NULL)
        goto cleanup;

    if (strcmp(action, "error") == 0) {
        // 如果action是error,则以异常的形式抛出警告
        PyErr_SetObject(category, message);
        goto cleanup;
    }

    /* Store in the registry that we've been here, *except* when the action
       is "always". */
    rc = 0;
   // 如果action是always,则始终会将警告信息显示出来。在默认动作中,只会在同一个位置显示一次,
   // 而设置为always的话,如果产生警告的操作位于某个循环体中,
   // 那么,在循环中,每次执行到该动作时都会显示出警告信息(而不管是不是在同一个位置)。
    if (strcmp(action, "always") != 0) {
        if (registry != NULL && registry != Py_None &&
                PyDict_SetItem(registry, key, Py_True) < 0)
            goto cleanup;
        // 如果action为ignore,则会忽略掉当前的警告,不会产生任何显示。
        else if (strcmp(action, "ignore") == 0)
            goto return_none;
        // 如果action为once,则匹配的警告只会在第一次出现时,进行显示,以后就不再显示该警告信息了。
        else if (strcmp(action, "once") == 0) {
            if (registry == NULL || registry == Py_None) {
                registry = get_once_registry();
                if (registry == NULL)
                    goto cleanup;
            }
            /* _once_registry[(text, category)] = 1 */
            rc = update_registry(registry, text, category, 0);
        }
        // 如果action为module,那么在同一个模块中, 只会显示一次匹配的警告信息。
        else if (strcmp(action, "module") == 0) {
            /* registry[(text, category, 0)] = 1 */
            if (registry != NULL && registry != Py_None)
                rc = update_registry(registry, text, category, 0);
        }
        // 如果action为default,也就是默认动作。那么,在同一个位置,只显示一次匹配的警告信息。
        else if (strcmp(action, "default") != 0) {
            PyObject *to_str = PyObject_Str(item);
            const char *err_str = "???";

            if (to_str != NULL)
                err_str = PyString_AS_STRING(to_str);
            PyErr_Format(PyExc_RuntimeError,
                        "Unrecognized action (%s) in warnings.filters:\n %s",
                        action, err_str);
            Py_XDECREF(to_str);
            goto cleanup;
        }
    }
   .........................
}


    Warning是所有警告类型的基类:

Warning
     +-- DeprecationWarning
     +-- PendingDeprecationWarning
     +-- RuntimeWarning
     +-- SyntaxWarning
     +-- UserWarning
     +-- FutureWarning
     +-- ImportWarning
     +-- UnicodeWarning
     +-- BytesWarning


    下面就对这些继承自Warning的警告类型,进行简单的介绍。

    DeprecationWarning:如果某些python功能,正在被弃用的话,就会产生DeprecationWarning。例如下面这段C代码:

Objects/exceptions.c 317:

static PyObject *
BaseException_get_message(PyBaseExceptionObject *self)
{
    PyObject *msg;

    /* if "message" is in self->dict, accessing a user-set message attribute */
    if (self->dict &&
        (msg = PyDict_GetItemString(self->dict, "message"))) {
        Py_INCREF(msg);
        return msg;
    }

    if (self->message == NULL) {
        PyErr_SetString(PyExc_AttributeError, "message attribute was deleted");
        return NULL;
    }

    // 异常对象的message属性,已经被弃用。
    /* accessing the deprecated "builtin" message attribute of Exception */
    if (PyErr_WarnEx(PyExc_DeprecationWarning,
                     "BaseException.message has been deprecated as "
                     "of Python 2.6", 1) < 0)
        return NULL;

    Py_INCREF(self->message);
    return self->message;
}


    例如下面这个脚本:

# DeprecationWarning.py
import traceback
import sys
import warnings

warnings.filters.insert(0, ('always', None, DeprecationWarning, None, 0))

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

try:
	5 / 0
except ZeroDivisionError as myerrobj:
	print 'err obj args:', myerrobj.args
	print 'err obj message:', myerrobj.message
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/warnings$ python DeprecationWarning.py 
err obj args: ('integer division or modulo by zero',)
DeprecationWarning.py:17: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
  print 'err obj message:', myerrobj.message
err obj message: integer division or modulo by zero
the following is traceback:
  File "DeprecationWarning.py", line 14, in <module>
    5 / 0
[18372 refs]
[email protected]:~/test/warnings$ 


    在Python 2.7.8中,默认情况下,DeprecationWarning在warnings.filters中的动作是'ignore'(也就是该警告会被忽略掉)。因此,脚本在一开始,就向warnings.filters的开头插入了always的动作。这样,当脚本使用myerrobj.message时,才会产生DeprecationWarning的警告信息。

    PendingDeprecationWarning:如果Python中的某些功能,在将来准备被弃用的话,就会产生该警告。

    RuntimeWarning:一些可疑的运行时代码,例如上面提到的tempnam这种不安全的方法在执行时,就会抛出RuntimeWarning

    SyntaxWarning:针对一些有问题的语法,所产生的警告。

    UserWarning:顾名思义,就是一般由用户代码生成的警告。例如下面这个脚本:

# UserWarning.py
import warnings

print 'now generate UserWarning'
print
warnings.warn('My warning', UserWarning)


    脚本的执行结果如下:

[email protected]:~/test/warnings$ python UserWarning.py 
now generate UserWarning

UserWarning.py:6: UserWarning: My warning
  warnings.warn('My warning', UserWarning)
[18364 refs]
[email protected]:~/test/warnings$ 


    脚本中使用了warnings模块中的warn方法,来产生UserWarning的警告信息。

    FutureWarning:某些结构或者函数,可能在未来会发生改变的警告。例如下面这个脚本:

# FutureWarning.py 
import warnings

# Probably there will be another arg in the future in this function
def some_function(arg1,arg2):
	warnings.warn(
		"Lookout! In the future there will be an arg3 argument for some_function",
		FutureWarning
	)
	return arg1 + arg2

some_function(1, 2)


    脚本的执行结果如下:

[email protected]:~/test/warnings$ python FutureWarning.py 
FutureWarning.py:8: FutureWarning: Lookout! In the future there will be an arg3 argument for some_function
  FutureWarning
[18364 refs]
[email protected]:~/test/warnings$ 


    在执行some_function时,他会提示用户,该函数将在以后发生变化,很可能会添加一个arg3的参数。

    ImportWarning:在导入模块时,可能会产生的警告。在Python 2.5的版本中,新增的警告类型。

    你可以在某个可疑的模块的开头,加入warnings.warn语句,当导入这个模块时,就会看到相应的警告信息了。例如下面这两个脚本:

# import_for_user.py
import warnings
# this module shouldt be imported, please... don't use it...
warnings.warn("Please, don't import the module 'import_for_user', it's dangerous.", ImportWarning)


# ImportWarning.py 
import traceback
import sys
import warnings

warnings.filters.insert(0, ('error', None, ImportWarning, None, 0))

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

print 'before import'
print

try:
	import import_for_user
except ImportWarning as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()

print
print 'after import'
print 


   上面在ImportWarning.py脚本中,会导入import_for_user模块,在该模块的开头就使用了warnings.warn来产生ImportWarning的警告,以提醒用户,该模块存在安全隐患。

    ImportWarning.py脚本的执行结果如下:

[email protected]:~/test/warnings$ python ImportWarning.py 
before import

catch <type 'exceptions.ImportWarning'>
err obj args: ("Please, don't import the module 'import_for_user', it's dangerous.",)
the following is traceback:
  File "ImportWarning.py", line 17, in <module>
    import import_for_user
  File "/home/black/test/warnings/import_for_user.py", line 4, in <module>
    warnings.warn("Please, don't import the module 'import_for_user', it's dangerous.", ImportWarning)

after import

[18370 refs]
[email protected]:~/test/warnings$ 


    UnicodeWarning:与unicode相关的警告,也是在Python 2.5中,新增的警告类型。例如下面这段C代码中就会产生该警告:

Objects/unicodeobject.c 6216:

PyObject *PyUnicode_RichCompare(PyObject *left,
                                PyObject *right,
                                int op)
{
    int result;

    result = PyUnicode_Compare(left, right);
    if (result == -1 && PyErr_Occurred())
        goto onError;

    /* Convert the return value to a Boolean */
    switch (op) {
    case Py_EQ:
        result = (result == 0);
        break;
    case Py_NE:
        result = (result != 0);
        break;
    case Py_LE:
        result = (result <= 0);
        break;
    case Py_GE:
        result = (result >= 0);
        break;
    case Py_LT:
        result = (result == -1);
        break;
    case Py_GT:
        result = (result == 1);
        break;
    }
    return PyBool_FromLong(result);

  onError:

    ................................................
    if (PyErr_ExceptionMatches(PyExc_TypeError)) {
        PyErr_Clear();
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }
    if (op != Py_EQ && op != Py_NE)
        return NULL;

    /* Equality comparison.

       This is a special case: we silence any PyExc_UnicodeDecodeError
       and instead turn it into a PyErr_UnicodeWarning.

    */
    if (!PyErr_ExceptionMatches(PyExc_UnicodeDecodeError))
        return NULL;
    PyErr_Clear();
    // 当要进行比较的两个对象,不能都转为Unicode对象时,就无法对它们进行常规的比较,
    // Python解释器就只能认为它们是不相等的,但是这是一种不推荐的形式,
    // 也可能暗示着你的脚本代码不够完善,因此,这种情况下,会产生UnicodeWarning警告。
    if (PyErr_Warn(PyExc_UnicodeWarning,
                   (op == Py_EQ) ?
                   "Unicode equal comparison "
                   "failed to convert both arguments to Unicode - "
                   "interpreting them as being unequal" :
                   "Unicode unequal comparison "
                   "failed to convert both arguments to Unicode - "
                   "interpreting them as being unequal"
            ) < 0)
        return NULL;
    result = (op == Py_NE);
    return PyBool_FromLong(result);
}


    例如下面这个脚本:

# UnicodeWarning.py 
import traceback
import sys
import warnings
warnings.filters.insert(0, ('always', None, UnicodeWarning, None, 0))

class MyCls():
	pass

a = unicode('hello')
b = 'world'.encode('zlib')
if(a == b):
	print 'a == b'
else:
	print
	print 'a != b'


    脚本的执行结果如下:

[email protected]:~/test/warnings$ python UnicodeWarning.py 
UnicodeWarning.py:12: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
  if(a == b):

a != b
[18760 refs]
[email protected]:~/test/warnings$ 


    BytesWarning:和字节数组相关的警告类型。例如下面这段C代码中,就会产生该警告:

Objects/bytearrayobject.c 1018:

static PyObject *
bytearray_richcompare(PyObject *self, PyObject *other, int op)
{
    Py_ssize_t self_size, other_size;
    Py_buffer self_bytes, other_bytes;
    PyObject *res;
    Py_ssize_t minsize;
    int cmp;

    /* Bytes can be compared to anything that supports the (binary)
       buffer API.  Except that a comparison with Unicode is always an
       error, even if the comparison is for equality. */
#ifdef Py_USING_UNICODE
    if (PyObject_IsInstance(self, (PyObject*)&PyUnicode_Type) ||
        PyObject_IsInstance(other, (PyObject*)&PyUnicode_Type)) {
        // 当bytearray与unicode字符串对象进行Py_EQ(等于的比较)的时候,如果设置了Py_BytesWarningFlag标志,
        // 就会产生警告。可以通过python的-b或者-bb选项来设置Py_BytesWarningFlag标志,
        // 当使用-bb时,除了会设置Py_BytesWarningFlag标志外,
        // 还会在warnings的filters列表中,将BytesWarning的action设置为error,
        // 因此,在提供了-bb选项时,会抛出BytesWarning的异常,
        // 而提供-b选项时,则只会显示出BytesWarning的警告信息。
        if (Py_BytesWarningFlag && op == Py_EQ) {
            if (PyErr_WarnEx(PyExc_BytesWarning,
                            "Comparison between bytearray and string", 1))
                return NULL;
        }

        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }
#endif
    ................................................
}


    例如下面这个脚本:

# BytesWarning.py
import traceback
import sys

def view_traceback():
	ex_type, ex_value, tb = sys.exc_info()
	traceback.print_tb(tb)
	del tb

b = bytearray([1,2,3])
try:
	if(b == u'123'):
		print "b == u'123'"
	else:
		print "b != u'123'"
except BytesWarning as myerrobj:
	print 'catch', type(myerrobj)
	print 'err obj args:', myerrobj.args
	print 'the following is traceback:'
	view_traceback()


    脚本的执行结果如下:

[email protected]:~/test/warnings$ python BytesWarning.py 
b != u'123'
[18363 refs]
[email protected]:~/test/warnings$ python -b BytesWarning.py 
BytesWarning.py:12: BytesWarning: Comparison between bytearray and string
  if(b == u'123'):
b != u'123'
[18365 refs]
[email protected]:~/test/warnings$ python -bb BytesWarning.py 
catch <type 'exceptions.BytesWarning'>
err obj args: ('Comparison between bytearray and string',)
the following is traceback:
  File "BytesWarning.py", line 12, in <module>
    if(b == u'123'):
[18365 refs]
[email protected]:~/test/warnings$ 


try/except/else:

    try/except/else的语法结构如下:

try:
	try_code...
except ExceptionType1 [(as|,) errobj]:
	exception_code...
except ExceptionType2 [(as|,) errobj]:
	exception_code...
except ExceptionType3 [(as|,) errobj]:
	exception_code...
........................
else:
	# no exception, execute elsecode
	elsecode...


    当try_code发生异常时,如果ExceptionType1异常类型没有捕获到异常对象的话,就再判断ExceptionType2异常类型,以此类推。如果try_code没有发生过异常的话,最后还会执行elsecode的代码。例如下面这个脚本:

# tryexcept.py 
for i in range(3):
	try:
		print 'i:',i
		if(i==0):
			5 / 0
		elif(i==1):
			a = b + 2
		else:
			a = 2
			print 'a = 2'
	except OverflowError, myerrobj:
		print 'catch', type(myerrobj)
	except IOError as myerrobj:
		print 'catch', type(myerrobj)
	except ZeroDivisionError as myerrobj:
		print 'catch', type(myerrobj)
	except Exception as myerrobj:
		print 'catch', type(myerrobj)
	else:
		# If there is no exception then execute this block.
		print 'pass, no exception'


    脚本的执行结果如下:

[email protected]:~/test/except$ python tryexcept.py 
i: 0
catch <type 'exceptions.ZeroDivisionError'>
i: 1
catch <type 'exceptions.NameError'>
i: 2
a = 2
pass, no exception
[18363 refs]
[email protected]:~/test/except$ 


    上面这个脚本在内部会执行的伪代码如下(伪代码是作者通过gdb调试器分析后,总结出来的内部流程的简化版):

// 当try_code发生异常时,会初始化errobj(异常对象实例)
try_code raise exception and init errobj
// 判断errobj异常对象的类型是否是OverflowError类型,
// 或者是否是OverflowError类型的子类,
// 如果是,则将异常对象与myerrobj变量名,在命名空间中,建立映射关系。
// 并跳转到相应的异常处理脚本处。
if(isSubClass(type(errobj), OverflowError))
	namespace.set('myerrobj', errobj)
	goto exception_code
// 判断errobj的类型是否是IOError类型,或者是否是IOError的子类。
elif(isSubClass(type(errobj), IOError))
	namespace.set('myerrobj', errobj)
	goto exception_code
// 判断errobj的类型是否是ZeroDivisionError类型,
// 或者是否是ZeroDivisionError的子类。
elif(isSubClass(type(errobj), ZeroDivisionError))
	namespace.set('myerrobj', errobj)
	goto exception_code
// 判断errobj的类型是否是Exception类型,
// 或者是否是Exception的子类。
elif(isSubClass(type(errobj), Exception))
	namespace.set('myerrobj', errobj)
	goto exception_code

// 如果上面的异常类型都不匹配的话,
// 就进入sys_excepthook的C 函数,去执行默认操作,
// 也就是通过PyErr_Display将异常信息显示出来,并停止脚本的执行。
goto default_code -> sys_excepthook -> PyErr_Display


    上面的isSubClass既可以判断是否是相同的类型,又可以判断是否是另一个类型的子类。

    try/except/else还有下面这种结构:

try:
	try_code...
except (ExceptionType1, ExceptionType2....) [(as|,) errobj]:
	exception_code...
else:
	# no exception, execute elsecode
	elsecode...


    当try_code发生异常时,如果生成的异常对象的类型,与ExceptionType1或者ExceptionType2相匹配(相同的类型或者是它们的子类)的话,就会去执行exception_code(用户自定义的异常处理代码)。例如下面这个脚本:

# tryexcept2.py 
for i in range(4):
	try:
		if(i==0):
			5 / 0
		elif(i==1):
			import os
			os.chdir('not_exists_dir')
		elif(i==2):
			open('not_exists')
		else:
			a = 2
			print 'a = 2'
	except (OverflowError, FloatingPointError, ZeroDivisionError), myerrobj:
		print '[first except code] catch', type(myerrobj)
	except (IOError, OSError) as myerrobj:
		print '[second except code] catch', type(myerrobj)
	else:
		# If there is no exception then execute this block.
		print 'pass, no exception'


    脚本的执行结果如下:

[email protected]:~/test/except$ python tryexcept2.py 
[first except code] catch <type 'exceptions.ZeroDivisionError'>
[second except code] catch <type 'exceptions.OSError'>
[second except code] catch <type 'exceptions.IOError'>
a = 2
pass, no exception
[18363 refs]
[email protected]:~/test/except$ 


    上面这个脚本,在内部执行时的伪代码如下:

try_code raise exception and init errobj

if(isSubClass(type(errobj), OverflowError) or 
   isSubClass(type(errobj), FloatingPointError) or
   isSubClass(type(errobj), ZeroDivisionError))
	namespace.set('myerrobj', errobj)
	goto [first except code]...
elif(isSubClass(type(errobj), IOError) or 
   isSubClass(type(errobj), OSError))
	namespace.set('myerrobj', errobj)
	goto [second except code]...

goto default_code -> sys_excepthook -> PyErr_Display


    当然,python 2.x中,try/except还有下面这种结构:

try:
	try_code
except ExceptionType as (arg1, arg2....):
	exception_code...


    该结构会直接将异常对象里的args元组中的成员,依次设置到arg1,arg2....中。例如下面这个脚本:

# tryexcept3.py
try:
	import os
	os.chdir('nonexists/testdir')
except OSError as (errno, errstr):
	print 'errno:', errno
	print 'errstr', errstr

print
try:
	import os
	os.chdir('nonexists/testdir')
except OSError as myerrobj:
	(errno, errstr) = myerrobj.args
	print 'catch', type(myerrobj)
	print 'errno:', errno
	print 'errstr', errstr


    可以看到,except OSError as (errno, errstr)其实本质上就相当于(errno, errstr) = myerrobj.args,也就是将异常对象中的args元组里的成员,依次设置到errno和errstr变量中。脚本的执行结果如下:

[email protected]:~/test/except$ python tryexcept3.py 
errno: 2
errstr No such file or directory

catch <type 'exceptions.OSError'>
errno: 2
errstr No such file or directory
[18363 refs]
[email protected]:~/test/except$ 


    except ExceptionType as (arg1, arg2....)在执行时,内部会通过下面这个C函数去完成args的迭代操作:

Python/ceval.c 3590:

static int
unpack_iterable(PyObject *v, int argcnt, PyObject **sp)
{
    int i = 0;
    PyObject *it;  /* iter(v) */
    PyObject *w;

    assert(v != NULL);
    // python会将errobj(异常对象实例)传递给参数v,并获取v的迭代器。
    it = PyObject_GetIter(v);
    if (it == NULL)
        goto Error;

    // argcnt表示你提供的(arg1, arg2...)元组的尺寸。
    for (; i < argcnt; i++) {
        // 迭代器在对errobj进行迭代时,内部会将errobj.args元组中的成员,作为结果给迭代出来。
        w = PyIter_Next(it);
        if (w == NULL) {
            /* Iterator done, via error or exhaustion. */
            // 如果你给出的(arg1, arg2...)元组的尺寸,超出了errobj.args元组的大小时,
            // 就会抛出ValueError异常。
            if (!PyErr_Occurred()) {
                PyErr_Format(PyExc_ValueError,
                    "need more than %d value%s to unpack",
                    i, i == 1 ? "" : "s");
            }
            goto Error;
        }
        *--sp = w;
    }
    ..............
}


    可以看到,如果你提供的元组尺寸,超出了异常对象实例中的args元组的大小时,会抛出ValueError异常。例如下面这个脚本:

# tryexcept4.py
try:
	import os
	os.chdir('nonexists/testdir')
except OSError as (errno, errstr, test):
	print 'errno:', errno
	print 'errstr', errstr


    脚本的执行结果如下:

[email protected]:~/test/except$ python tryexcept4.py 
Traceback (most recent call last):
  File "tryexcept4.py", line 5, in <module>
    except OSError as (errno, errstr, test):
ValueError: need more than 2 values to unpack
[18363 refs]
[email protected]:~/test/except$ 


    上面抛出的异常对象的args元组中,只包含两个成员,而脚本提供的(errno, errstr, test)需要接受三个成员,就抛出了ValueError的异常。因此,在使用这种结构时,你必须清楚的知道异常对象的args元组中会包含多少个成员。

    在Python 3.x中也不再支持except ExceptionType as (arg1, arg2....)的语法。可以参考下面这段C代码:

Objects/exceptions.c 229:

static PyObject *
BaseException_getitem(PyBaseExceptionObject *self, Py_ssize_t index)
{
     // 前面的unpack_iterable函数,在对异常对象进行迭代操作时,最终会进入到该函数。
     // 下面提示:在3.x中,获取异常对象里的成员是not supported(不被支持的!),需要使用args属性。
    if (PyErr_WarnPy3k("__getitem__ not supported for exception "
                       "classes in 3.x; use args attribute", 1) < 0)
        return NULL;
    // 如果是Python 2.x,在获取异常对象中的成员时,会自动从args元组中获取成员。
    return PySequence_GetItem(self->args, index);
}


    这段代码说明,Python 3.x中,异常对象是不可迭代的,也就无法使用except ExceptionType as (arg1, arg2....)的语法了。

try/finally:

    try/finally的语法结构如下:

try:
	try_code
finally:
	must_execute_code


    不管try_code中的代码是否发生了异常,最后都会去执行finally中的must_execute_code代码块。例如下面这个脚本:

# tryfinally.py
try:
	try:
		5 / 0
	except:
		print 'catch exception'
finally:
	print "I must be executed......."
	print

print '---------------------------'
print

try:
	5 / 0
finally:
	print "I must be executed......."
	print


    脚本的执行结果如下:

[email protected]:~/test/except$ python tryfinally.py 
catch exception
I must be executed.......

---------------------------

I must be executed.......

Traceback (most recent call last):
  File "tryfinally.py", line 15, in <module>
    5 / 0
ZeroDivisionError: integer division or modulo by zero
[18363 refs]
[email protected]:~/test/except$ 


    可以看到,如果你定义了异常的处理代码的话,自定义的异常处理代码会先执行,最后再执行finally中的代码。如果没有定义异常处理代码的话,那么finally中的代码会先执行,最后才会执行Python默认的异常处理过程(因为默认的异常处理过程会在所有的用户脚本都执行完后,才会执行)。

    从2.5版本开始,还可以直接使用try...except...finally的结构。而之前的版本,只能将try...except嵌入到try...finally中。例如下面这个脚本:

# tryfinally2.py
try:
	5 / 0
except:
	print 'catch exception'
finally:
	print "I must be executed......."


    脚本的执行结果如下:

[email protected]:~/test/except$ python tryfinally2.py 
catch exception
I must be executed.......
[18363 refs]
[email protected]:~/test/except$ 


raise:

    我们可以通过raise来手动抛出异常。raise的语法格式如下:

raise ExceptionType [, args [, traceback]]

    ExceptionType表示异常类型,args表示需要设置的异常参数(比如具体的异常信息等),traceback对象则用于设置栈追踪信息。例如下面这个脚本:

# raise.py 
import traceback, sys

print 'example 1:'

try:
	raise OSError
except OSError as myerrobj:
	print 'catch', type(myerrobj)
	print 'args:', myerrobj.args
	oserror_tb = sys.exc_info()[2]

print '\nexample 2:'

try:
	raise Exception, (101, "I'm exception", "hello world") 
except Exception as myerrobj:
	print 'catch', type(myerrobj)
	print 'args:', myerrobj.args

print '\nexample 3:'

try:
	raise ValueError, "invalid value error"
except:
	try:
		(exc_type, exc_value, exc_tb) = sys.exc_info()
		raise exc_type, exc_value, exc_tb
	except:
		traceback.print_exc()

print '\nexample 4:'

try:
	raise ValueError, "invalid value error"
except:
	(exc_type, exc_value, exc_tb) = sys.exc_info()
	raise exc_type, exc_value, oserror_tb


    脚本的执行结果如下:

[email protected]:~/test/except$ python raise.py 
example 1:
catch <type 'exceptions.OSError'>
args: ()

example 2:
catch <type 'exceptions.Exception'>
args: (101, "I'm exception", 'hello world')

example 3:
Traceback (most recent call last):
  File "raise.py", line 24, in <module>
    raise ValueError, "invalid value error"
ValueError: invalid value error

example 4:
Traceback (most recent call last):
  File "raise.py", line 7, in <module>
    raise OSError
ValueError: invalid value error
[18363 refs]
[email protected]:~/test/except$ 


    example 4中,由于将traceback设置为了OSError发生时的traceback,因此就显示的是raise OSError的信息了。

用户自定义异常:

    我们可以对Python内建的异常类型进行继承,来创建出自己的异常类型。例如,前面的exception.py例子中就定义了一个继承自Exception的mydefined_except异常类型。

    我们也可以在官方的异常层次结构的基础上,向下定义出自己的异常拓扑结构出来,例如下面这个脚本:

# UserDefined.py 
# define Python user-defined exceptions
class MyError(Exception):
	"""Base class for other exceptions"""
	def __init__(self, code, msg):
		super(MyError, self).__init__(code, msg)
		self.code = code

class ValueTooSmallError(MyError):
	"""Raised when the input value is too small"""
	def __init__(self, code, msg):
		super(ValueTooSmallError, self).__init__(code, msg)
		self.msg = (msg)

class ValueTooLargeError(MyError):
	"""Raised when the input value is too large"""
	def __init__(self, code, msg):
		super(ValueTooLargeError, self).__init__(code, msg)
		self.msg = (msg)

# our main program
# user guesses a number until he/she gets it right

# you need to guess this number
number = 10

while True:
	try:
		s = raw_input("Enter a number: ")
		if(s == ''):
			continue
		elif(not s.isdigit()):
			print 'must input digital'
			continue
		i_num = int(s)
		if i_num < number:
			raise ValueTooSmallError(-1, "This value is too small, try again!")
		elif i_num > number:
			raise ValueTooLargeError(-2, "This value is too large, try again!")
		break
	except ValueTooSmallError as myerrobj:
		print 'error args:', myerrobj.args
		print 'errorcode:', myerrobj.code
		print myerrobj.msg
		print
	except ValueTooLargeError as myerrobj:
		print 'error args:', myerrobj.args
		print 'errorcode:', myerrobj.code
		print myerrobj.msg
		print

print("Congratulations! You guessed it correctly.")


    脚本的执行结果如下:

[email protected]:~/test/except$ python UserDefined.py 
Enter a number: 12
error args: (-2, 'This value is too large, try again!')
errorcode: -2
This value is too large, try again!

Enter a number: 9
error args: (-1, 'This value is too small, try again!')
errorcode: -1
This value is too small, try again!

Enter a number: 8
error args: (-1, 'This value is too small, try again!')
errorcode: -1
This value is too small, try again!

Enter a number: 11
error args: (-2, 'This value is too large, try again!')
errorcode: -2
This value is too large, try again!

Enter a number: 10
Congratulations! You guessed it correctly.
[18523 refs]
[email protected]:~/test/except$ 


    脚本对异常类型做了如下拓展:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      .............................
      +-- Warning
           .............................
      +-- MyError
           +-- ValueTooSmallError
           +-- ValueTooLargeError


    MyError继承自Exception,ValueTooSmallError与ValueTooLargeError则继承自MyError。

    此外,MyError增加了自己的code属性。ValueTooSmallError与ValueTooLargeError则增加了自己的msg属性。

结束语:

    与Python中的异常相关的内容,就介绍到这里。这篇文章写完后,短期内(也许很长很长的一段时间内)都不会有更新了。然后,就没有然后了。。。。

    Goodbye! o(∩_∩)o~~

    去做你害怕的事,害怕自然就会消逝。

——  拉尔夫·沃尔多·爱默生
 
上下篇

下一篇: 暂无

上一篇: Python基本的I/O操作 (五)

相关文章

Python的数字类型及相关的类型转换函数

Python的time模块

Python的calendar模块

Python基本的I/O操作 (四)

Python循环语句

Python用户自定义函数