异常处理机制—常见异常及处理方法总结
AI研习图书馆,发现不一样的精彩世界
一、引言
异常,即一个事件,该事件会在程序执行过程中发生,影响程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。异常是Python对象,表示一个错误。当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。
注意到一个 NameError 错误被抛出,同时 Python 还会打印出检测到的错误发生的位置。这就是一个错误处理器(Error Handler)为这个错误所做的事情。
Python的异常处理能力十分强大,有很多内置异常,可向用户准确反馈出错信息。在Python中,异常也是对象,可对它进行操作。
BaseException是所有内置异常的基类,但用户定义的类并不直接继承BaseException,所有的异常类都是从Exception继承,且都在exceptions模块中定义。Python自动将所有异常名称放在内建命名空间中,因此程序不必导入exceptions模块,即可使用异常。一旦引发而且没有捕捉SystemExit异常,程序执行就会自动终止。如果交互式会话遇到一个未被捕捉的SystemExit异常,会话就会终止。
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 # 与资源使用相关的警告的基类。被默认警告过滤器忽略
在做网络爬虫时,requests是一个十分常用的模块,所以在这里专门探讨一下requests模块相关的异常。
from requests.exceptions import ConnectionError, ReadTimeout
requests模块内置异常类的层次结构如下:
IOError
+-- RequestException # 处理不确定的异常请求
+-- HTTPError # HTTP错误
+-- ConnectionError # 连接错误
| +-- ProxyError # 代理错误
| +-- SSLError # SSL错误
| +-- ConnectTimeout(+-- Timeout) # (双重继承,下同)尝试连接到远程服务器时请求超时,产生此错误的请求可以安全地重试。
+-- Timeout # 请求超时
| +-- ReadTimeout # 服务器未在指定的时间内发送任何数据
+-- URLRequired # 发出请求需要有效的URL
+-- TooManyRedirects # 重定向太多
+-- MissingSchema(+-- ValueError) # 缺少URL架构(例如http或https)
+-- InvalidSchema(+-- ValueError) # 无效的架构,有效架构请参见defaults.py
+-- InvalidURL(+-- ValueError) # 无效的URL
| +-- InvalidProxyURL # 无效的代理URL
+-- InvalidHeader(+-- ValueError) # 无效的Header
+-- ChunkedEncodingError # 服务器声明了chunked编码但发送了一个无效的chunk
+-- ContentDecodingError(+-- BaseHTTPError) # 无法解码响应内容
+-- StreamConsumedError(+-- TypeError) # 此响应的内容已被使用
+-- RetryError # 自定义重试逻辑失败
+-- UnrewindableBodyError # 尝试倒回正文时,请求遇到错误
+-- FileModeWarning(+-- DeprecationWarning) # 文件以文本模式打开,但Requests确定其二进制长度
+-- RequestsDependencyWarning # 导入的依赖项与预期的版本范围不匹配
Warning
+-- RequestsWarning # 请求的基本警告
import requests
from requests import ReadTimeout
def get_page(url):
try:
response = requests.get(url, timeout=1)
if response.status_code == 200:
return response.text
else:
print('Get Page Failed', response.status_code)
return None
except (ConnectionError, ReadTimeout):
print('Crawling Failed', url)
return None
def main():
url = 'https://www.baidu.com'
print(get_page(url))
if __name__ == '__main__':
main()
1.3 用户自定义异常
此外,你也可以通过创建一个新的异常类拥有自己的异常,异常应该是通过直接或间接的方式继承自Exception类。
class MyError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
try:
raise MyError('类型错误')
except MyError as e:
print('My exception occurred', e.msg)
2. 异常捕获
此外,与Python异常相关的关键字主要有:
Python解释器检测到错误,触发异常(也允许程序员自定义触发异常)。程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)。如果捕捉成功,则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理。
Python解释器执行程序,检测到了一个错误时,触发异常,异常触发后且没被处理的情况下,程序就会在当前异常处终止,后面的代码就不会运行,谁会去用一个运行着突然就崩溃的软件。因此,你必须提供一种异常处理机制来增强你程序的健壮性与容错性。良好的容错能力,能够有效的提高用户体验,维持业务的稳定性。
通常情况下,程序运行中的异常可以分为两类:语法错误和逻辑错误。首先,我们必须知道,语法错误跟异常处理无关,所以我们在处理异常之前,必须避免语法上的一些基础性错误。
if num1.isdigit():
int(num1) #正统程序放到了这里,其余的属于异常处理范畴
elif num1.isspace():
print('输入的是空格,就执行我这里的逻辑')
elif len(num1) == 0:
print('输入的是空,就执行我这里的逻辑')
else:
print('其他情情况,执行我这里的逻辑')
使用if判断式可以进行异常处理,但是if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误,就需要写重复的if来进行处理。而且在程序中频繁的写与程序本身无关,与异常处理有关的if,会使得你的代码可读性极其的差。
2. Python提供的特定语法结构
try:
被检测的代码块
except 异常类型:
try中一旦检测到异常,就执行这个位置的逻辑
2.2 捕获指定异常
try:
<语句>
except <异常名>:
print('异常说明')
try:
<语句>
except Exception:
print('异常说明')
如果你想要的效果是:无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么只有一个Exception就足够了。
如果你想要的效果是:对于不同的异常,我们需要定制不同的处理逻辑,那就需要用到多分支了。我们可以使用多分支+万能异常来处理异常。使用多分支优先处理一些能预料到的错误类型,一些预料不到的错误类型应该被最终的万能异常捕获。需要注意的是,万能异常一定要放在最后,否则就没有意义了。
单分支示例:
#单分支只能用来处理指定的异常情况
try:
a
except NameError as e:
#except与as+变量名搭配使用,打印变量名会直接输出报错信息
print(e) #name 'a' is not defined
多分支示例:
l1 = [('电脑',16998),('鼠标',59),('手机',8998)]
while 1:
for key,value in enumerate(l1,1):
print(key,value[0])
try:
num = input('>>>')
price = l1[int(num)-1][1]
except ValueError:
print('请输入一个数字')
except IndexError:
print('请输入一个有效数字')
#这样通过异常处理可以使得代码更人性化,用户体验感更好
2.3 捕获多个异常
捕获多个异常有两种方式,第一种是一个except同时处理多个异常,不区分优先级:
try:
<语句>
except (<异常名1>, <异常名2>, ...):
print('异常说明')
try:
<语句>
except <异常名1>:
print('异常说明1')
except <异常名2>:
print('异常说明2')
except <异常名3>:
print('异常说明3')
该种异常处理语法的规则是:
执行try下的语句,如果引发异常,则执行过程会跳到第一个except语句 箬第一个except中定义的异常与引发的异常匹配,则执行该except中的语句 如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制 如果所有except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中
2.4 异常中的else
如果判断完没有某些异常之后还想做其他事,就可以使用下面这样的else语句。
try:
<语句>
except <异常名1>:
print('异常说明1')
except <异常名2>:
print('异常说明2')
else:
<语句> # try语句中没有异常则执行此段代码
2.5 异常中的finally
try...finally... 语句无论是否发生异常都将会执行最后的代码。
try:
<语句>
finally:
<语句>
2.6 raise主动触发异常
raise [Exception [, args [, traceback]]]
try:
block
except:
traceback.print_exc()
# 可能发生异常的代码
except 异常类型1 as 变量名:
print(变量名) # 变量名存储的是具体的错误信息
except 异常类型2 as 变量名:
print(变量名) # 变量名存储的是具体的错误信息
except Exception as 变量名:
print(变量名) # 变量名存储的是具体的错误信息
else:
print('如果以上代码没有发生异常以及异常处理工作就执行这里的代码')
print('一般情况下else中的代码用来下结论')
# logging模块
finally:
print('不管代码是否有异常都会执行,且在函数中遇到return仍然会执行')
print('一般情况下用于这个函数中资源的回收')