器→工具, 编程语言

Python异常捕获与处理

钱魏Way · · 64 次浏览

什么是异常?

异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。异常是Python对象,表示一个错误。当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。

当一个未捕获的异常发生时,Python将结束程序并打印一个堆栈跟踪信息,以及异常名和附加信息。具体如下:

>>> a = 1/0
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    a = 1/0
ZeroDivisionError: division by zero

广义上的错误分为错误和异常

  • 错误通常指的是语法错误,可以人为避免
  • 异常是指在语法逻辑正确的而前提下,出现的问题

在Python中捕获和处理异常的主要目的:

  • 错误处理:在运行时出现错误的情况下,应用程序可能会无条件终止。使用异常处理,我们可以处理失败的情况并避免程序终止。
  • 代码分离:错误处理可以帮助我们将错误处理所需的代码与主逻辑分离。与错误相关的代码可以放置在“ except ”块中,该块将其与包含应用程序逻辑的常规代码隔离开来。
  • 错误区分:帮助我们隔离执行过程中遇到的不同类型的错误。我们可以有多个“ except”块,每个块处理一种特定类型的错误。

其他方面的应用:

  • 事件通知:异常也可以作为某种条件的信号,而不需要在程序里传送结果标志或显式地测试它们。
  • 特殊情形处理:有时有些情况是很少发生的,把相应的处理代码改为异常处理会更好一些。
  • 特殊的控制流:异常是一个高层次的”goto”,可以把它作为实现特殊控制流的基础。如反向跟踪等。

Python自带的异常处理机制非常强大,提供了很多内置异常类,可向用户准确反馈出错信息。Python是面向对象语言,认为一切皆对象,所以异常也是对象。Python异常处理机制中的BaseException是所有内置异常的基类,但用户定义的类并不直接继承BaseException,所有的异常类都是从Exception继承,且都在exceptions模块中定义。Python自动将所有异常名称放在内建命名空间中,所以程序不必导入exceptions模块即可使用异常。

Python内置异常类继承层次结构如下:

BaseException  # 所有异常的基类
 -- SystemExit  # 解释器请求退出
 -- KeyboardInterrupt  # 用户中断执行(通常是输入^C)
 -- GeneratorExit  # 生成器(generator)发生异常来通知退出
 -- Exception  # 常规异常的基类
      -- StopIteration  # 迭代器没有更多的值
      -- StopAsyncIteration  # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
      -- ArithmeticError  # 各种算术错误引发的内置异常的基类
      |    -- FloatingPointError  # 浮点计算错误
      |    -- OverflowError  # 数值运算结果太大无法表示
      |    -- ZeroDivisionError  # 除(或取模)零 (所有数据类型)
      -- AssertionError  # 当assert语句失败时引发
      -- AttributeError  # 属性引用或赋值失败
      -- BufferError  # 无法执行与缓冲区相关的操作时引发
      -- EOFError  # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发
      -- ImportError  # 导入模块/对象失败
      |    -- ModuleNotFoundError  # 无法找到模块或在在sys.modules中找到None
      -- LookupError  # 映射或序列上使用的键或索引无效时引发的异常的基类
      |    -- IndexError  # 序列中没有此索引(index)
      |    -- KeyError  # 映射中没有这个键
      -- MemoryError  # 内存溢出错误(对于Python 解释器不是致命的)
      -- NameError  # 未声明/初始化对象 (没有属性)
      |    -- UnboundLocalError  # 访问未初始化的本地变量
      -- OSError  # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类
      |    -- BlockingIOError  # 操作将阻塞对象(e.g. socket)设置为非阻塞操作
      |    -- ChildProcessError  # 在子进程上的操作失败
      |    -- ConnectionError  # 与连接相关的异常的基类
      |    |    -- BrokenPipeError  # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入
      |    |    -- ConnectionAbortedError  # 连接尝试被对等方中止
      |    |    -- ConnectionRefusedError  # 连接尝试被对等方拒绝
      |    |    -- ConnectionResetError    # 连接由对等方重置
      |    -- FileExistsError  # 创建已存在的文件或目录
      |    -- FileNotFoundError  # 请求不存在的文件或目录
      |    -- InterruptedError  # 系统调用被输入信号中断
      |    -- IsADirectoryError  # 在目录上请求文件操作(例如 os.remove())
      |    -- NotADirectoryError  # 在不是目录的事物上请求目录操作(例如 os.listdir())
      |    -- PermissionError  # 尝试在没有足够访问权限的情况下运行操作
      |    -- ProcessLookupError  # 给定进程不存在
      |    -- TimeoutError  # 系统函数在系统级别超时
      -- ReferenceError  # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
      -- RuntimeError  # 在检测到不属于任何其他类别的错误时触发
      |    -- NotImplementedError  # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现
      |    -- RecursionError  # 解释器检测到超出最大递归深度
      -- SyntaxError  # Python 语法错误
      |    -- IndentationError  # 缩进错误
      |         -- TabError  # Tab和空格混用
      -- SystemError  # 解释器发现内部错误
      -- TypeError  # 操作或函数应用于不适当类型的对象
      -- ValueError  # 操作或函数接收到具有正确类型但值不合适的参数
      |    -- UnicodeError  # 发生与Unicode相关的编码或解码错误
      |         -- UnicodeDecodeError  # Unicode解码错误
      |         -- UnicodeEncodeError  # Unicode编码错误
      |         -- UnicodeTranslateError  # Unicode转码错误
      -- Warning  # 警告的基类
           -- DeprecationWarning  # 有关已弃用功能的警告的基类
           -- PendingDeprecationWarning  # 有关不推荐使用功能的警告的基类
           -- RuntimeWarning  # 有关可疑的运行时行为的警告的基类
           -- SyntaxWarning  # 关于可疑语法警告的基类
           -- UserWarning  # 用户代码生成警告的基类
           -- FutureWarning  # 有关已弃用功能的警告的基类
           -- ImportWarning  # 关于模块导入时可能出错的警告的基类
           -- UnicodeWarning  # 与Unicode相关的警告的基类
           -- BytesWarning  # 与bytes和bytearray相关的警告的基类
           -- ResourceWarning  # 与资源使用相关的警告的基类。被默认警告过滤器忽略。

异常捕获与处理

我们不能保证编写的程序永远正确运行,异常捕获与处理的宗旨是保证程序在最坏的情况下得到的问题被妥善管理。

Python 中,用try except语句块捕获并处理异常,其基本语法结构如下所示:

try:
    可能产生异常的代码块
except [ (Error1, Error2, ... ) [as e] ]:
    处理异常的代码块1
except [ (Error3, Error4, ... ) [as e] ]:
    处理异常的代码块2
except  [Exception]:
    处理其它异常
else:
    没有异常时执行的代码块
finally:
    不管有没有异常都会执行的代码块

try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。

如果当try后的语句执行时发生异常,Python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息)。如果在try子句执行时没有发生异常,Python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。

代码实例:

try:
    file_reader = open("a.json")
    print(file_reader.read())
except FileNotFoundError:
    print("The File Cannot be Found")
except:
    print("This is the Generic Error")
finally:
    print("Closing File Reader...")
    file_reader.close()

Exception类是所有Python异常类的父类,所以except Exception将可以捕获任何异常,换句话说,它是万能异常处理句式。有一种错误没办法抓住:缩进错误。

raise:手动引发异常

有时候,异常可以作为代码运行的标志,通过主动触发异常可以改变代码的运行路线,从而提高代码健壮性。

主动触发异常需使用raise关键字,其语法结构如下:

raise [Exception [, args [, traceback]]]

示例:

try:
    a = int(input("Enter a positive integer value: "))
    if a <= 0:
        raise ValueError("This is not a positive number!!")
except ValueError as ve:
    print(ve)

语句中Exception是异常的类型(例如,ValueError)参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是”None”。最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。

自定义异常

你可以通过创建一个新的异常类来拥有自己的异常。异常类继承自 Exception 类,可以直接继承,或者间接继承,例如:

class MyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


try:
    raise MyError(2 * 2)
    # raise MyError('oops!')
except MyError as e:
    print('My exception occurred, value:', e.value)

断言:assert条件

Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。断言可以在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况,例如我们的代码只能在 Linux 系统下运行,可以先判断当前系统是否符合条件。

语法格式如下:

assert expression

等价于:

if not expression:
    raise AssertionError(arguments)

实例:

import sys
assert ('linux' in sys.platform), "该代码只能在 Linux 下执行"

assert看上去不错,然而用起来并不爽。就比如有人告诉你程序错了,但是不告诉哪里错了。很多时候这样的assert还不如不写。常见方案是使用测试框架或其他包代替:py.test、unittest、ptest、assertpy…

traceback 获取详细的异常信息

try…except…的输出结果只能让你知道报了这个错误,却不知道在哪个文件哪个函数哪一行报的错。使用 traceback 模块可以非常清楚的了解具体错误内容在哪:

import traceback

try:
    1 / 0
except Exception as e:
    traceback.print_exc()

输出:

Traceback (most recent call last):
File "test_traceback.py", line 3, in <module>
1/0
ZeroDivisionError: integer division or modulo by zero

Python程序的traceback信息均来源于一个叫做traceback object的对象,而这个traceback object通常是通过函数sys.exc_info()来获取的:

import sys


def func1():
    raise Exception("--func1 exception--")


def test1():
    try:
        func1()
    except Exception as e:
        print(e)


def test2():
    try:
        func1()
    except Exception as e:
        exc_type, exc_value, exc_traceback_obj = sys.exc_info()
        print("exc_type: %s" % exc_type)
        print("exc_value: %s" % exc_value)
        print("exc_traceback_obj: %s" % exc_traceback_obj)


if __name__ == '__main__':
    test1()
    test2()

输出结果:

--func1 exception--
exc_type: <class 'Exception'>
exc_value: --func1 exception--
exc_traceback_obj: <traceback object at 0x0000024D2F6A22C8>

Process finished with exit code 0

通过以上示例我们可以看出,sys.exc_info()获取了当前处理的exception的相关信息,并返回一个元组,元组的第一个数据是异常的类型,第二个返回值是异常的value值,第三个就是我们要的traceback object.

有了traceback object我们就可以通过traceback module来打印和格式化traceback的相关信息。

参考链接:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注