Python 定义自己的异常类

Python 定义自己的异常类,在刚开始使用Python时,我不敢在代码中编写自定义异常类。但是定义自己的错误类型有很多好处,比如可以清楚地显示出潜在的错误,让函数和模块更具可维护性。自定义错误类型还可用来提供额外的调试信息。

这些特性都有助于改进Python代码,使其更易于理解、调试和维护。下面通过几个例子循序渐进地轻松学习定义自己的异常类。本节将逐个介绍其中必须掌握的要点。
假设需要对应用程序中表示人名的输入字符串进行验证,你编写了下面这个简单的人名验证函数:

def validate(name):
    if len(name) < 10:
        raise ValueError

如果函数验证失败就会引发ValueError异常。这看上去已经很有Python特色了,还算可以吧。

不过使用像ValueError这样的“高级”泛型异常类有一个缺点。假设函数是其他库的一部分,同事在不了解其内部实现的情况下直接使用。那么在名字验证失败时,栈调试回溯中的内容会如下所示:

>>> validate('joe')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    validate('joe')
  File "<input>", line 3, in validate
    raise ValueError
ValueError

这个栈回溯用处不大。虽然知道出了问题,并且问题与某种“错误的值”有关,但为了解决问题,同事肯定会查看validate()的实现。但阅读代码需要时间,而且通常会耗费很长时间。

幸运的是还有更好的办法,即引入自定义异常类型来表示名字验证失败。下面将基于Python的内置ValueError创建新的异常类,但用更显式的名称来说明问题:

class NameTooShortError(ValueError):
    pass

def validate(name):
    if len(name) < 10:
        raise NameTooShortError(name)

现在有了能够“顾名思义”的NameTooShortError异常类型,它扩展自内置的ValueError类。一般情况下自定义异常都是派生自Exception这个异常基类或其他内置的Python异常,如ValueErrorTypeError——取决于哪个更合适。

另外,注意在validate函数中实例化自定义异常时,将name变量传递给了构造函数,这样能为他人提供更好的栈回溯内容:

>>> validate('jane')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    validate('jane')
  File "<input>", line 3, in validate
    raise NameTooShortError(name)
NameTooShortError: jane

再次尝试从他人的角度体会一下上面的输出。当发生错误时,自定义异常类能清楚地描述发生的状况。

即使是在自己的代码库上工作也是如此,结构良好的代码在数周或数月之后依然很容易维护。

花费30秒定义一个简单的异常类就能让代码更加可读。现在继续前进,还有更多内容需要掌握。

无论是公开发布Python软件包,还是为公司创建可重用的模块,最好为模块创建一个自定义异常基类,然后从中派生所有其他异常。

下面为一个模块或包中的所有异常创建自定义的异常层次结构。第一步是声明一个基类,其他所有的具体错误都会继承这个类:

class BaseValidationError(ValueError):
    pass

所有的“实际”错误类都可以从这个错误基类派生出来,从而组成一个优雅且整洁的异常层次结构:

class NameTooShortError(BaseValidationError):
    pass

class NameTooLongError(BaseValidationError):
    pass

class NameTooCuteError(BaseValidationError):
    pass

这样用户就可以编写try...except语句来处理软件包中所有的自定义错误,无须手动捕获各个具体的异常:

try:
    validate(name)
except BaseValidationError as err:
    handle_validation_error(err)

用户仍然可以捕获更具体的异常,不过如果想以宽泛的方式处理,那么可以捕获这个自定义基类,不用再一股脑地捕获所有异常了。捕获所有异常通常是一种反模式,会默默吞下并隐藏无关的错误,让程序难以调试。

当然,还可以进一步扩展这种思想,将异常根据逻辑分组,形成更精细的子层次结构。但要小心,这样很容易引入不必要的复杂性。

总之,编写自定义异常类能更好地在代码中采纳 “请求原谅比请求许可更容易”(easier to ask for forgiveness than permission,EAFP )这种Python式的编程风格。

关键要点

  • 定义自己的异常类型能让代码清楚地表达出自己的意图,并易于调试。

  • 要从Python内置的Exception类或特定的异常类(如ValueErrorKeyError)派生出自定义异常。

  • 可以使用继承来根据逻辑对异常分组,组成层次结构。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程