一、说明
Python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误是,都会创建一个异常对象。
如果编写了处理该异常的代码,程序将继续执行,否则,显示一个traceback,其中包含有关异常的报告。
异常使用try-except代码块处理。
程序运行中的异常可以分为两类:语法错误和逻辑错误。首先,必须知道,语法错误跟异常处理无关,所以在处理异常之前,必须避免语法上的错误。
二、演示
print(2/0)
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 # 与资源使用相关的警告的基类。被默认警告过滤器忽略。
三、异常处理方式
关键字 | 说明 |
try/except | 捕获异常并处理 |
pass | 忽略异常 |
as | 定义异常实例(except MyError as e) |
else | 如果try中语句没有引发异常,则执行else中的语句 |
finally | 无论是否出现异常,都执行的代码 |
raise | 抛出/引发异常 |
3.1 使用if判断式
first_number = 1 second_number = 0 if second_number != 0: result = first_number/second_number*third_number print(result) else: print("零不能为除数")
使用if判断式可以异常处理,但是if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误你需要写重复的if来进行处理。
而且在你的程序中频繁的写与程序本身无关,与异常处理有关的if,会使得你的代码可读性极其的差。
3.2 try-except 单分支结构
first_number = 1 second_number = 0 try: result = first_number/second_number print(result) except ZeroDivisionError: print("零不能为除数")
3.3 try-except 多分支结构
first_number = 1 second_number = 2 third_number = '3' try: result = first_number/second_number*third_number print(result) except ZeroDivisionError: print("零不能为除数") except TypeError: print("数字类型不对")
注意:当运行到"/second_number"时,如果出错,则不会运行下面的代码。也就是不会运行"*third_number"
3.4 万能异常
在python的异常中,有一个万能异常:Exception,他可以捕获任意异常。它是一把双刃剑,有利有弊,我们要视情况使用
如果你想要的效果是,无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么只有一个Exception就足够了。
如果你想要的效果是,对于不同的异常我们需要定制不同的处理逻辑,那就需要用到多分支了。我们可以使用多分支+万能异常来处理异常。使用多分支优先处理一些能预料到的错误类型,一些预料不到的错误类型应该被最终的万能异常捕获。
需要注意的是,万能异常一定要放在最后,否则就没有意义了。
3.4 try-except-else
first_number = 1 second_number = 2 third_number = 3 try: result = first_number/second_number*third_number except TypeError: print("数字类型不对") except ZeroDivisionError: print("零不能为除数") else: # 当try执行无异常的时候会执行else语句 print(result)
3.5 try-***-finally
first_number = 1 second_number = 0 third_number = 3 try: result = first_number/second_number*third_number except TypeError: print("数字类型不对") except ZeroDivisionError: print("零不能为除数") else: # 当try执行无异常的时候会执行else语句 print(result) finally: # 不管try语句中是否报错,都会执行finally print("执行完毕")
3.6 主动触发异常
def not_zero(num): try: if num == 0: raise ValueError('参数错误') return num except Exception as e: print(e) not_zero(0)
3.7 自定义异常
class CustomError(Exception): def __init__(self, errorInfo): super().__init__(self) # 初始化父类 self.errorinfo = errorInfo def __str__(self): return self.errorinfo if __name__ == '__main__': try: raise CustomError('自定义异常') except CustomError as e: print(e)
四、总结
4.1 异常处理遵循以下原则
4.1.1 注意异常的粒度,不推荐在try中放入过多的代码。
在try中放入过多的代码带来的问题是如果程序抛出异常,将比较难定位。
4.1.2 谨慎使用单独的except语句处理所有异常
最好定位具体的异常,同样也不推荐使用except Exception或者except StandardError来破获异常。
如果不得不使用单独的except语句,最好能够使用raise语句将异常抛出向上层传递。
4.1.3 注意异常捕获的顺序,在合适的层次处理异常
如果异常能够在捕获的位置被处理,那么应该及时处理,不能处理的应该以适当的方式向上层抛出。
向上次传递需要警惕异常丢失。
4.1.4 使用更为友好的异常信息。
照顾用户和其他开发者。
4.2 避免finally中可能发生的陷阱
4.2.1 异常屏蔽
def finally_text(): while True: try: print("I am running") raise IndexError("r") # 抛出IndexError异常 except NameError as e: print("NameError happended {}".format(e)) break finally: print("finally executed") break finally_text()
当 try 块中发生异常的时候,如果在 except 语句中找不到对应的异常处理,异常将会被临时保存起来,
当 finally 执行完毕的时候,临时保存的异常将会再次被抛出,但如果 finally 语句中产生了新的异常或者执行了 return 或者 break 语句,
那么临时保存的异常将会被丢失,从而导致异常屏蔽。
4.2.1 永不运行代码
def return_text(n): try: if n <= 0: raise ValueError("n can't be negative") else: # else 分支的代码永不执行 return n except ValueError as e: print(e) finally: print("end") return -1 print(return_text(-5)) print(return_text(5))
当n<=0,返回-1比较容易理解。
当n>0时,由于存在finally语句,在执行else语句之前会先执行finally中的语句,此时因为finally语句中有return-1,程序直接返回,永远不会执行else语句。
不推荐在finally中使用return语句进行返回。不仅带来误解,还会带来非常严重的错误。