异常

允许使用异常, 但必须谨慎使用.

定义:

异常是一种跳出正常的控制流, 以处理错误或其它异常情况的方法.

优点:

处理正常情况的控制流不会和错误处理代码混在一起. 在特定情况下, 它也能让控制流跳出多层调用帧. 例如, 一步跳出N多层嵌套的函数, 而不必逐层传递错误代码.

缺点:

可能导致控制流晦涩难懂. 调用库函数时容易忘记处理异常.

结论:

使用异常时必须遵守特定要求:

  1. 优先使用合适的内置异常类. 比如, 用 ValueError 表示前置条件错误 (例如给必须为正数的参数传入了负值). 不要使用 assert 语句来验证公开API的参数值. 应该用 assert 来保证内部正确性, 不应该用 assert 来纠正参数或表示意外情况. 若要用异常来表示意外情况, 应该用 raise. 例如:

    正确:

    def connect_to_next_port(self, minimum: int) -> int:
       """连接到下一个可用的端口.
    
       参数:
           minimum: 一个大于等于 1024 的端口号.
    
       返回:
           新的最小端口.
    
       抛出:
           ConnectionError: 没有可用的端口.
       """
       if minimum < 1024:
           # 注意这里抛出 ValueError 的情况没有在文档里说明,因为 API 的
           # 错误用法应该是未定义行为.
           raise ValueError(f'最小端口号至少为 1024,不能是 {minimum}.')
       port = self._find_next_open_port(minimum)
       if port is None:
           raise ConnectionError(
               f'未能通过 {minimum} 或更高的端口号连接到服务.')
       assert port >= minimum, (
           f'意外的端口号 {port}, 端口号不应小于 {minimum}.')
       return port

    错误:

    def connect_to_next_port(self, minimum: int) -> int:
       """连接到下一个可用的端口.
    
       参数:
           minimum: 一个大于等于 1024 的端口号.
    
       返回:
           新的最小端口.
       """
       assert minimum >= 1024, '最小端口号至少为 1024.'
       port = self._find_next_open_port(minimum)
       assert port is not None
       return port
    1. 模块或包可以定义自己的异常类型, 这些类必须继承已有的异常类. 异常类型名应该以 Error 为后缀, 并且不应该有重复 (例如 foo.FooError).

    3.永远不要使用 except: 语句来捕获所有异常, 也不要捕获 Exception 或者 StandardError , 除非你想:

    1. 重新抛出异常.
    2. 在程序中创造一个隔离点, 记录并抑制异常, 让异常不再继续传播. 这种写法可以用在线程的最外层, 以避免程序崩溃.

    如果你使用这种写法, Python 将非常宽容. except: 真的会捕获任何错误, 包括拼写错误的符号名、 sys.exit() 调用、 Ctrl+C 中断、单元测试错误和各种你不想捕获的错误.

    4.最小化 try/except 代码块中的代码量. try 的范围越大, 就越容易把你没想到的那些能抛出异常的代码囊括在内. 这样的话, try/except 代码块就掩盖了真正的错误.

    5.用 finally 表示无论异常与否都应执行的代码. 这种写法常用于清理资源, 例如关闭文件.