目录

Python异常处理的优秀实践

前几天在掘金上看到一篇文章 《Python 工匠: 异常处理的三个好习惯》,董伟明的 “Python 之美” 公众号也转发了,感觉不错。

文章主要讲了三个 Python 异常处理时的好习惯:

  • 只做最精确的异常捕获;
  • 别让异常破坏抽象一致性;
  • 异常处理不应该喧宾夺主。

下面分别简要说明一下。

只做最精确的异常捕获

目的是不捕获到不必要的异常,不掩盖其他的问题。

精确包括两部分:

  • 捕获精确,即只捕获那些可能会抛出异常的语句块;
  • 类型精确,即错误类型而要尽可能的具体;

捕获精确使 try 中的代码最精确,类型精确使 except 中错误类型精确。

别让异常破坏抽象一致性

文章中介绍了一个特别好的想法,在 web 开发中利用 raise error 来返回错误消息,我感觉有几个好处:

  1. 可以将错误消息集中定义在某处,方便修改;
  2. 抛错的地方 import 很简洁,只需要 import 该种错误;
  3. 在接错误的地方,只要接错误类的父类的异常,就可以接到所有需要返回错误消息的场景;

在此之外,作者举了一个例子,阐述了抽象一致性的必要性。我们在进行错误处理时,要注意到:

  • 让模块只抛出与当前抽象层级一致的异常;
  • 在必要的地方进行异常包装与转换(参考 requests 库对 urllib3 库异常的封装)。

异常处理不应该喧宾夺主

这一条基本属于优化代码可读性。主要是说,不应该在代码中都是 try…except,而掩盖了正常的业务逻辑。

我认为,在代码中使用多个 try…except 并无不妥,文章提供的使用 with 上下文管理器来抛出异常的方式确实不错,但在同时接多个异常时,会造成单行代码过长的问题(其实也不是问题,可以通过改造管理器类传入错误类列表改善)。

不过还是把作者写的 demo 转过来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class raise_api_error:
    """captures specified exception and raise ApiErrorCode instead

    :raises: AttributeError if code_name is not valid
    """
    def __init__(self, captures, code_name):
        self.captures = captures
        self.code = getattr(error_codes, code_name)

    def __enter__(self):
        # 方法将在刚进入上下文时调用
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # 该方法将在退出上下文时调用
        # exc_type, exc_val, exc_tb 分别表示该上下文内抛出的
        # 异常类型、异常值、错误栈
        if exc_type is None:
            return False

        if exc_type == self.captures:
            raise self.code from exc_val
        return False

参考链接