在Python中,BaseException是所有异常的基类。但是通常我们不应该直接使用它来捕获异常(虽然它都可以捕获),取而代之的是我们需要针对于具体的业务场景,自定义适合的异常类型。
BaseException的实现在CPython源码中的Objects/exceptions.c这个文件中。如下文:
/*
* Exception extends BaseException
*/
SimpleExtendsException(PyExc_BaseException, Exception,
"Common base class for all non-exit exceptions.");
再往下看你会发现,BaseException这个类定义了所有的异常需要用到的属性和方法。我们经常用的Exception这个异常其实只是继承了BaseException,其它的什么都没有做。
其它直接继承自BaseException的异常类型有:
- GeneratorExit
- SystemExit
- KeyboardInterrupt
其它的内置异常类型都继承自Exception。完整的Python2和Python3的内置异常类型关系如下图:
BaseException的初始化方法(init(*args))保存了所有通过args传递进来的参数。这个在Python2和Python3的实现是一样的:
static int
BaseException_init(PyBaseExceptionObject *self, PyObject *args, PyObject *kwds)
{
if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds))
return -1;
Py_INCREF(args);
Py_XSETREF(self->args, args);
return 0;
}
args参数唯一被用到的地方就是它的str方法,它会将args转换成一个字符串并返回给调用方:
static PyObject *
BaseException_str(PyBaseExceptionObject *self)
{
switch (PyTuple_GET_SIZE(self->args)) {
case 0:
return PyUnicode_FromString("");
case 1:
return PyObject_Str(PyTuple_GET_ITEM(self->args, 0));
default:
return PyObject_Str(self->args);
}
}
对应到Python代码的话,就是这样:
def __str__(self):
if len(self.args) == 0:
return ""
if len(self.args) == 1:
return str(self.args[0])
return str(self.args)
定义你自己的异常
在Python中,异常可以在程序的任何位置被抛出来,异常的种类也可以是五花八门。一般来说,我们可以使用Exception来捕获大部分的异常,但是更多的时候我们还是应该尽可能的明确要捕获的异常类型,毕竟如果只是抛出来一个Exception的话,对于debug其实并没有太大的帮助。
定义一个定制的异常类型非常容易,只需要(大部分场景)继承自Exception即可:
class MyException(Exception):
pass
一个导航APP
假设我们要做一个导航APP,那可以定义基础异常:
class CarError(Exception):
"""Basic exception for errors raised by cars"""
def __init__(self, car, msg=None):
if msg is None:
# Set some default useful error message
msg = "An error occured with car %s" % car
super(CarError, self).__init__(msg)
self.car = car
然后再定义个车祸的异常:
class CarCrashError(CarError):
"""When you drive too fast"""
def __init__(self, car, other_car, speed):
super(CarCrashError, self).__init__(
car, msg="Car crashed into %s at speed %d" % (other_car, speed))
self.speed = speed
self.other_car = other_car
之后,在程序中就可以捕获该异常了:
try:
drive_car()
except CarCrashError as msg:
# do sth
这样的异常捕获,对于后续的debug来说,显然要比直接捕获Exception要友好、有用的多。
再次抛出异常
我自己的编程习惯有一个原则: 异常要在自己控制的范围内。因此如果异常不属于当前函数,就把异常再次抛出,交由上层逻辑进行处理。例如:
class MylibError(Exception):
"""Generic exception for mylib"""
def __init__(self, msg, original_exception):
super(MylibError, self).__init__(msg + (": %s" % original_exception))
self.original_exception = original_exception
def foo():
try:
requests.get("http://example.com")
except requests.exceptions.ConnectionError as e:
raise MylibError("Unable to connect", e)
def request_url():
try:
foo()
except MylibError as msg:
# do sth
这个例子可以看到,当异常在本函数内无法处理时,就把它通过raise关键词再次跑出去。交给上层逻辑进行处理。 这样做的好处是每一层的任务更加清晰。不会出现异常捕获混乱不堪的局面。
异常捕获和日志
通常来说,异常都是需要处理的。因此比较常见的做法是将他们记录到日志里,方便后续的相关同学进行debug。 只是捕获异常,不做任何记录时,会让修bug的同学无从下手。