Skip to content

Latest commit

 

History

History
249 lines (172 loc) · 9.36 KB

异常和警告.md

File metadata and controls

249 lines (172 loc) · 9.36 KB

异常和警告

通常我们会在程序的编程的过程中就可以预期到一些会导致程序错误退出的事件,这种预期中的异常事件被称作异常(Exception).

而有些时候我们判断需要通过一些途径向用户发送消息以提醒用户在程序中的某些条件下不能保证引发异常并终止程序但又有一定的威胁.例如,当程序使用过时的模块时,我们就应该使用警告(warnings).在python中警告也是一种异常.

异常和警告是python语言中框架/服务开发重要工具.它为框架/服务提供了业务流的提醒和退出机制.

本节的预备知识包括:

异常

异常即是一个事件,该事件会在程序执行过程中发生并影响程序的正常执行.一般情况下在Python无法正常处理程序时就会发生一个异常.异常也是Python得对象,表示一个错误.当Python脚本发生异常时我们可以捕获它进行处理(服务),也可以让他终止程序的执行(脚本).

python的标准异常可以在官网文档中查看,常用的如下:

异常名称 描述
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告

异常捕获处理

try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理. 如果你不想在异常发生时结束你的程序,只需在try里捕获它

try:
    <语句>        #运行别的代码
except <名字><语句>        #如果在try部份引发了'name'异常
except <名字><数据>:
    <语句>        #如果引发了'name'异常,获得附加的数据
else:
    <语句>        #如果没有异常发生
finally:
    <语句>    #退出try时总会执行

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

  • 如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕控制流就通过整个try语句(除非在处理异常时又引发新的异常).

  • 如果在try后的语句里发生了异常却没有匹配的except子句,异常将被递交到上层的try或者到程序的最上层(这样将结束程序并打印缺省的出错信息).

  • 如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句.

无论是否有异常,finally都将被执行,它一般作为存放收尾动作的地方,但是注意finally有个陷阱--如果主干上的错误分支中没有对应的捕捉,那么他将被保存在一个临时的位置,而如果同时finally中有错误,则这个临时的错误会被finally中的错误给替代.

使用异常捕获处理语句应该尽量精准地包裹可能有错误的代码.

Python遵守尽早失败原则,认为程序应该尽早报告错误.例如,Python中没有"未定义"的值:在初始化之前引用变量会报错;如果k不存在,my_dict[k]会抛出异常(JavaScript则不然).因为尽早失败原则,python代码中try语句会比较多,但即便如此,我们的单个try结构应该尽量精准的包裹可能出错的代码.

抛出异常

我们可以使用raise语句自己触发异常 raise语法格式如下

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

自定义异常

如果自己写模块,最好自定义模块的异常,一方面可以更好的分析代码,另一方面也让用模块的用户更容易追踪错误.

class Networkerror(RuntimeError):
    def __init__(self, arg):
        self.args = arg
try:
    raise Networkerror("Bad hostname")
except Networkerror as e:
    print(e.args)
('B', 'a', 'd', ' ', 'h', 'o', 's', 't', 'n', 'a', 'm', 'e')

异常类的__note__字段[3.11]

BaseException新增了一个可变属性__note__.这个属性可以作为异常的注释,会连同错误信息一起打印出来.通常用于在捕获异常后追加信息:

try:
    1 / 0
except Exception as e:
    e.__note__ = "Custom message"
    raise e

异常组[3.11]

异常组这个概念的提出是为了让程序在一个时间内同时抛出/处理多个异常.举个例子:

raise ExceptionGroup('bad param', [ValueError('bad value'), TypeError('bad type')])

这样就抛出了一个包含ValueErrorTypeError两个异常的异常组.

而捕获异常组则需要使用Except*语法,它和原本的Except语法不同之处仅在于Except仅捕获一个异常而Except*可以捕获多个异常.

比如下面的写法

try:
    raise ExceptionGroup('msg', [FooError(1), FooError(2), BazError()])
except* SpamError:
    ...
except* FooError:
    ...

则两个except*分支都会被触发.而且except* FooError分支可以被触发两次且except*捕获的异常会当作异常组处理

当然由于ExceptionGroup本身也继承自BaseException,因此ExceptionGroup也是个异常,也可以使用Except语法直接捕获,而Except*并不能直接捕获异常组本身:

try:
    raise ExceptionGroup('msg', [FooError(1), FooError(2), BazError()])
except ExceptionGroup as eg:
    ...

需要注意Except*可以被多次执行而Except只会被执行一次,这是一个根本性的变化.在之前只有Except时我们可以将每个分支作为程序的终点,而Except*使用时需要小心不能这样做.

输出警告

警告不应被像异常一样抛出以中断业务流,但应该被输出以提醒用户,在python中有专门做这个事情的标准库warnings.

import warnings

抛出警告

抛出警告使用warnings.warn(message:str, warning:Warning)来实现,它不会像用raise抛出一样中断业务流

raise DeprecationWarning("Deprecation")
print("next")
---------------------------------------------------------------------------

DeprecationWarning                        Traceback (most recent call last)

<ipython-input-7-d712af4440d8> in <module>()
----> 1 raise DeprecationWarning("Deprecation")
      2 print("next")


DeprecationWarning: Deprecation
warnings.warn("Deprecation", DeprecationWarning)
print("next")
next


/Users/huangsizhe/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:1: DeprecationWarning: Deprecation
  """Entry point for launching an IPython kernel.

捕获警告

警告的捕获也不使用try语句,而是使用上下文管理器warnings.catch_warnings结合行为过滤器warnings.simplefilter或者warnings.filterwarnings进行.

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    fxn()

其中simplefilter的参数ignore是指定捕获后行为用的,可选的有:

取值 描述
"error" 将匹配的警告转化为异常
"ignore" 从不打印匹配的警告
"always" 始终打印匹配的警告
"default" 打印出现警告的每个位置的首次匹配警告
"module" 打印出现警告的每个模块的首次匹配警告
"once" 不管位置如何,只打印首次出现的匹配警告