一、概要
一个程序即使没有任何语法错误,即使解题的逻辑也正确,在执行的时候仍然可能出现 各种“运行时错误”,导致程序无法按照预定的步骤顺利执行、正常结束。其后果是要么由系 统强行中止程序的运行,要么程序带着错误继续运行而得出错误的结果。这类运行时错误称 为异常或例外(exception)。产生异常的原因是复杂而多样的,既有程序设计的问题,也有运行环境的问题,如除数为零、用户输入数据的类型或个数不对、列表索引越界,写入文件的时候,磁盘满了,或者从网络抓取数据,网络突然断掉了等等。
如果一个程序很容易受到异常的影响而崩溃(即中止执行),那就不是好的程序,因为程序崩溃意味着无法完成预定的计算,不能满足用户的需求。另外,程序崩溃时系统一般会输出一堆错误消息,这些消息对程序员来说没啥大不了,但对普通用户来说则是难以理解的一 堆技术术语。用户不知道发生了什么,也不知道该如何处理。
因此,程序员必须在程序中加入处理错误的代码,以便在发生错误的情况下能自己处理 错误,使程序错误对用户是不可见的。这样的程序在发生错误的情况下也能正常结束而非崩 溃,并且显示给用户的也是可理解的友好的信息。我们称这样的程序是健壮的(robust)
程序开发时,很难将所有的特殊情况都处理的面面俱到,通过异常捕获可以针对突发事件做集中的处理,从而保证程序的稳定性和健壮性
二、为什么要使用异常
- 错误处理,当python检查程序运行时的错误就引发异常,你可以在程序里捕捉和处理这些错误,或者忽略它们。
- 事件通知,异常也可以作为某种条件的信号,而不需要在程序里传送结果标志或显式地测试它们。
- 特殊情形处理,有时有些情况是很少发生的,把相应的处理代码改为异常处理会更好一些。
- 奇特的控制流,异常是一个高层次的"goto",可以把它作为实现奇特的控制流的基础。如反向跟踪等。
二、异常处理
1、概要
程序运行时如果发生错误,就“抛出”一个异常,而系统能够“捕获”这个异常并执行特定的异常处理代码,即使一条语句或表达式在语法上是正确的,当试图执行它时也可能会引发错误。运行期检测到的错误称为异常,并且程序不会无条件的崩溃,
2、捕获异常
-
语法格式
try: <语句块> except 异常类名 as err: <异常处理语句块>
-
解释
- 执行<语句块>,如果一切正常,执行结束后控制转向 try-except 的下一条语句
- 如果执行过程中发生了异常,则控制转向异常处理语句块,执行结束后控制转向 try-except 的下一条语句。
-
举个栗子
try name = 'laowang' print(name[7]) except: print('索引越界') ''' IndexError: string index out of range '''
索引越界错误发生之后,控制自动转到 except 子句下面的处理代码,处理完毕还可以继 续执行程序的其他语句。如果没有错误,则忽略 except 部分。
-
优点
使用异常处理机制可以使程序的核心算法代码与错误处理代码相互分离,从而保持程序结构的清晰。如果要了解程序的主要算法,只需读 try 下面的语句块,例如
try: do_something1() do_something2() do_something3() except: do_error()
显然这种形式的代码既能保持算法逻辑的清晰完整,又能实现错误检测,
3、捕获多个异常
-
概要
以上用到的简单形式的 try 语句不加区分地对所有错误进行相同的处理,如果需要对不同错误类型进行不同的处理,则可使用更精细的控制
-
语法格式
try: <语句> #运行核心代码 except <名字>: <语句> #如果在try部份引发了'name'异常 except <名字>,<数据>: <语句> #如果引发了'name'异常,获得附加的数据 else: <语句> #如果没有异常发生
-
解释
执行<语句块>,如果一切正常,执行结束后控制转向 try-except 的下一条语句;
如果执行过程中发生了异常,则系统依次检查各个 except 子句试图找到与所发生的异常相匹 配的错误类型。
如果找到,就执行相应的异常处理语句块,
如果找不到则执行最后一个 except 子句下的缺省异常处理语句块。
异常处理结束后控制转到 try-except 的下一条语句。
-
注意: 最后一个不含错误类型的 except 子句是可选的,用于捕获所有未预料到的错误类型。如果未
使用最后这个 except 子句,那么当异常与所有错误类型都不匹配时,则由 Python 解释器捕获 异常并处理之
-
举个栗子
""" 异常类型:当python解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型 """ try: # 提示用户输入一个整数 num = int(input("请输入一个整数:")) # 使用10除以该数字并输出 result = 10 / num print(result) except ZeroDivisionError: print("计算机不能除零") except ValueError: print("您输入的不是整数。。") print("程序正常结束:over。。")
4、finally
-
说明
python总会执行finally子句,无论try子句执行时是否发一异常。
-
语法格式
try: # 可能产生异常的代码 pass except 异常类型 as result: # 产生异常,执行此处 print('产生错误了:%s'% result) finally: # 无论是否产生异常,此处的代码一定会被执行 print('真高兴')
-
解释
- 如果没有发生异常,python运行try子句,然后是finally子句,然后继续。
- 如果在try子句发生了异常,python就会回来执行finally子句,然后把异常递交给上层try,控制流不会通过整个try语句
-
举个栗子
5、raise
-
说明
允许程序员强制抛出一个指定的异常
-
语法格式
raise <name> raise <name>,<data> raise
-
举个栗子
raise NameError('手动抛出异常') def thorw_err(): raise Exception("抛出一个异常") def throw_error(): raise Exception("抛出一个异常") #异常被抛出,print函数无法执行 print("大吉大利!今晚吃鸡!")
如果你需要明确一个异常是否抛出,但不想处理它,raise 语句可以让你很简单的重新抛出该异常
try: raise NameError('手动抛出异常') except NameError: print('不好了!不好了!出BUG了') raise
-
注意事项
要抛出的异常由 raise的唯一参数标识。它必需是一个异常实例或异常类(继承自 Exception的类)。
6、完整的异常
-
说明
在实际开发中,为了能够处理复杂的异常情况,要使用完整的异常语法。
-
语法格式
try: # 尝试执行的代码 pass except 错误类型1: # 针对于错误类型1,对应的的代码处理 pass except (错误类型2, 错误类型3): # 针对于错误类型2 和 3,对应的的代码处理 pass except Exception as result: # 打印错误信息,未知类型异常 print(result) else: # 没有异常才会执行的代码 pass finally: # 无论是否有异常,都会执行的代码 print("无论是否有异常,此处代码都会执行")
-
举个栗子
try: f = open("testfile", "w") f.write("用于测试异常!!") except IOError: print("Error: 没有找到文件或读取文件失败") else: print (内容写入文件成功") finally: f.close()
三、用户自定义异常
1、概括
有些时候我们需要创建自己的异常类,这个类就像其他类一样,只要是确保从Exception类继承
不管是间接继承还是直接继承,也就是是继承其他内建异常类也是可以的
2、自定义异常
-
写一个类继承Exception类
class NetworkError(Exception): pass
-
或者继承他的子类
class HostnameError(NetworkError): pass class TimeoutError(NetworkError): pass class ProtocolError(NetworkError): pass
-
说明
自定义异常类应该总是继承自内置的
Exception
类, 或者是继承自那些本身就是从Exception
继承而来的类。 尽管所有类同时也继承自BaseException
,但你不应该使用这个基类来定义新的异常。BaseException
是为系统退出异常而保留的,比如KeyboardInterrupt
或SystemExit
以及其他那些会给应用发送信号而退出的异常。 因此,捕获这些异常本身没什么意义。 这样的话,假如你继承BaseException
可能会导致你的自定义异常不会被捕获而直接发送信号退出程序运行。
四、异常的传递
1、异常的嵌套
如果try嵌套,那么如果里面的try没有捕获到这个异常,那么外面的try会接收到这个异常,然后进行处理,如果外边的try依然没有捕获到,那么再进行传递。。。
2、异常的传递
- 说明
异常的传递,当函数执行出现异常,会将异常传递给函数的调用方
如果传递到主程序,仍然没有异常处理,程序才会被终止
详细描述:如果一个异常是在一个函数中产生的,例如函数A---->函数B---->函数C,而异常是在函数C中产生的,那么如果函数C中没有对这个异常进行处理,那么这个异常会传递到函数B中,如果函数B有异常处理那么就会按照函数B的处理方式进行执行;如果函数B也没有异常处理,那么这个异常会继续传递,以此类推。。。如果所有的函数都没有处理,那么此时就会进行异常的默认处理,即通常见到的那样
提示:
- 在开发中,可在主函数中增加异常捕获
- 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中
- 这样就不需要在代码中,增加大量的异常捕获,能够保证代码的整洁
五、总结
1、使用原则
大致来说,Python的异常在使用上都很简单。异常背后真正的技巧在于确定except分句要具体或多通用,以及try语句中要包括多少代码。
-
try语句中要包括多少代码。
简要原则,经常会失败的运算一般都应该包装在try语句内。例如:和系统状态衔接的运算(文件开启,套接字调用等等)就是try的主要候选者。在简单的脚本中,你会希望这类运算失败时终止程序,而不是被捕捉或被忽略。如果是一个重大的错误,更应如此。Python的错误会产生有用的错误信息,而且这通常就是所期望的最好的结果。应该try/finally中实现终止动作,从而保证它们的执行。这个语句形式可执行代码,无论异常是否发生。偶尔,把对大型函数的调用包装在单个try语句内,而不是让函数本身零散着放入若干try的语句中。这样会更方便。这样的话,函数中的异常就会往上传递到调用周围的try,而你也可以减少函数中的代码量。
-
避免空except语句
如果使用空except语句,可能拦截到异常嵌套结构中较高层的try处理器所期待的事件这类代码可能会捕捉无关的系统异常。如内存错误,程序错误,迭代停止以及系统推出等等,都会在Python中引发异常。这里异常通常是不应该拦截的。
捕捉太少:使用基于类的分类
2、注意事项
一个try就有一个except或多个except,不要只捕获不处理
-
慎用异常:
- 找到python的内置异常
- 理解python的内置异常分别对应什么情况
- 阅读你的代码,找到你的代码里可能会抛出内置异常的地方
- 仅对这几行代码做异常处理
- 假设你无法知道你的代码会抛出什么异常,那么你的异常处理便是无效的
3、开发建议
- 实现异常类,比较好的做法是:将所有自定义异常放在一个单独的文件中(例如:
exceptions.py
或errors.py
),许多标准模块也都是这样做的。 - 既然自定义异常是类,那么它必然可以实现一个普通类能做的所有事情。但一般而言,应该尽量保持简单、简洁。大多数实现都是声明一个自定义基类,并从这个基类派生出其他的(由程序引发的)异常类。这是 Python 中实现自定义异常的标准方法,但并不仅限于这种方式。
六、附:内建异常体系
-
继承结构
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StandardError | +-- BufferError | +-- ArithmeticError | | +-- FloatingPointError | | +-- OverflowError | | +-- ZeroDivisionError | +-- AssertionError | +-- AttributeError | +-- EnvironmentError | | +-- IOError | | +-- OSError | | +-- WindowsError (Windows) | | +-- VMSError (VMS) | +-- EOFError | +-- ImportError | +-- LookupError | | +-- IndexError | | +-- KeyError | +-- MemoryError | +-- NameError | | +-- UnboundLocalError | +-- ReferenceError | +-- RuntimeError | | +-- NotImplementedError | +-- SyntaxError | | +-- IndentationError | | +-- TabError | +-- SystemError | +-- TypeError | +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning
-
内置异常说明
**异常名称****** **描述****** BaseException 所有异常的基类 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 用户代码生成的警告