一、引言 在软件开发或项目开发中,当我们调试Python程序时,经常会报出一些异常,严重影响程序的性能。一般情况下,程序抛出异常主要有两方面的原因:一方面,可能是编写程序时由于疏忽或者考虑不全造成了错误,这时就需要根据异常Traceback到出错点,进行分析修正;另一方面,有些异常是不可避免的,但我们可以对异常进行捕获处理,防止程序终止。在编程过程中,为了增加友好性、容错性和健壮性,在程序出现bug时一般不会将错误信息直接显示给用户,而是显示一个提示的页面,通俗来说就是不让用户看见大黄页!还有,有时我们不希望一个小bug的出现让整个程序都面临崩溃,例如自动退出或者蓝屏,我们需要抓取这个错误异常,并进行处理,让用户能够继续使用下去。异常,即一个事件,该事件会在程序执行过程中发生,影响程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。异常是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模块相关的异常。 要调用requests模块的内置异常,只需要“from requests.exceptions import xxx”就可以了,例如: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 # 请求的基本警告 举个例子:Python内置了一个ConnectionError异常,这里可以不用导包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类。 下面创建了一个MyError类,基类为Exception,用于在异常触发时输出更多的信息。在try语句块中,抛出用户自定义的异常后执行except部分,变量 e 是用于创建MyError类的实例。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的异常捕获常用try...except...结构,把可能发生错误的语句放在try模块里,用except来处理异常,每一个try,都必须至少对应一个except。此外,与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('异常说明')
在Python的异常中,有一个万能异常:Exception,他可以捕获任意异常。但它是一把双刃剑,有利有弊,我们要视情况使用。
如果你想要的效果是:无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么只有一个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 异常中的finallytry...finally... 语句无论是否发生异常都将会执行最后的代码。 2.6 raise主动触发异常可以使用raise语句自己触发异常,raise语法格式如下:raise [Exception [, args [, traceback]]] 语句中Exception是异常的类型(如ValueError),参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是"None"。最后一个参数是跟踪异常对象,也是可选的(在实践中很少使用)。当程序发生异常时,Python能“记住”引发的异常以及程序的当前状态。Python还维护着traceback对象,其中含有异常发生时与函数调用堆栈有关的信息。记住,异常可能在一系列嵌套较深的函数调用中引发。程序调用每个函数时,Python会在“函数调用堆栈”的起始处插入函数名。一旦异常被引发,Python会搜索一个相应的异常处理程序。如果当前函数中没有异常处理程序,当前函数会终止执行,Python会搜索当前函数的调用函数,并以此类推,直到发现匹配的异常处理程序,或者Python抵达主程序为止。这一查找合适的异常处理程序的过程就称为“堆栈辗转开解”(StackUnwinding)。解释器一方面维护着与放置堆栈中的函数有关的信息,另一方面也维护着与已从堆栈中“辗转开解”的函数有关的信息。try: block
except:
traceback.print_exc()
try: # 可能发生异常的代码 except 异常类型1 as 变量名: print(变量名) # 变量名存储的是具体的错误信息 except 异常类型2 as 变量名: print(变量名) # 变量名存储的是具体的错误信息 except Exception as 变量名: print(变量名) # 变量名存储的是具体的错误信息 else: print('如果以上代码没有发生异常以及异常处理工作就执行这里的代码') print('一般情况下else中的代码用来下结论') # logging模块 finally: print('不管代码是否有异常都会执行,且在函数中遇到return仍然会执行') print('一般情况下用于这个函数中资源的回收')
|