**学习目标**:
了解Python异常处理的常用知识,掌握异常处理的基本用法。
为增强程序的健壮性, 我们也需要考虑异常处理方面的内容。 例如, 在读取文件时需要考虑文件不存在、 文件格式不正确等异常情况。 这就是本节要介绍的异常处理。
一、第一个异常示例 在数学中, 任何整数都不能除以0, 如果在计算机程序中将整数除以0, 则会引发异常。
1 2 3 4 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) res = int(a) / int(b) print(f'{a} / {b} = {res} ' )
由于0不能做除数,所以当我们将第二个数输入0的时候,程序就会报错
1 2 3 ZeroDivisionError Traceback (most recent call last) ... ZeroDivisionError: division by zero
Traceback
信息是异常堆栈信息,描述了程序运行的过程及引发异常的相关信息。通过分析异常堆栈信息,我们可以分析程序哪里出了问题。在Python中,异常类命名的主要后缀有Exception、Error和Warning
,也有少数几个没有以这几个后缀命名,我们将它们统一翻译为异常。
二、Python内置异常 Python的异常处理能力是很强大的,它有很多内置异常,可向用户准确反馈出错信息。在Python中,异常也是对象,可对它进行操作。BaseException是所有内置异常的基类,但用户定义的类并不直接继承BaseException,所有的异常类都是从Exception继承,且都在exceptions模块中定义。Python自动将所有异常名称放在内建命名空间中,所以程序不必导入exceptions模块即可使用异常。一旦引发而且没有捕捉SystemExit异常,程序执行就会终止。如果交互式会话遇到一个未被捕捉的SystemExit异常,会话就会终止。内置异常类的层次结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning
详细说明参考:https://docs.python.org/3/library/exceptions.html#base-classes
三、异常捕获 我们不能防止用户输入0, 但在出现异常后我们能捕获并处理异常, 不至于让程序发生终止并退出。 亡羊补牢, 为时未晚。 因此,当发生异常时,我们就需要对异常进行捕获,然后进行相应的处理。python的异常捕获常用try…except…结构,把可能发生错误的语句放在try模块里,用except来处理异常,每一个try,都必须至少对应一个except。此外,与python异常相关的关键字主要有:
关键字
关键字说明
try/except
捕获异常并处理
pass
忽略异常
as
定义异常实例(except MyError as e)
else
如果try中的语句没有引发异常,则执行else中的语句
finally
无论是否出现异常,都执行的代码
raise
抛出/引发异常
异常捕获有很多方式,下面分别进行讨论。
1. try except 异常捕获是通过try-except语句实现的, 基本的try-except语句的语法如下。
1 2 3 4 try : 可能会引发异常的语句 except 异常类型: 处理异常
在try代码块中包含在执行过程中可能引发异常的语句, 如果没有发生异常, 则跳到except代码块执行, 这就是异常捕获。try-except语句的执行流程如下。
首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)。
如果没有异常发生,忽略 except 子句,try 子句执行后结束。
如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。
如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。
示例代码
1 2 3 4 5 6 7 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : res = int(a) / int(b) print(f'{a} / {b} = {res} ' ) except : print('除数不能为0, 异常' )
如果不指定具体的异常类型,except可以捕获在try中发生的所有异常信息。如果指定异常类型,则except只能捕获对应的异常类型信息。推荐大家在except中指定具体的异常类型。
修改示例代码如下:
1 2 3 4 5 6 7 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : res = int(a) / int(b) print(f'{a} / {b} = {res} ' ) except ZeroDivisionError as e: print(f'除数不能为0, 异常信息为:{e} ' )
2. 多个except代码块 多条语句可能引发不同的异常,每一种异常采用不同的处理方式。针对这种情况,我们可以在try后跟对个except语句。语法如下:
1 2 3 4 5 6 7 try : 可能会引发异常的语句 except 异常类型1 : 处理异常 except 异常类型2 : 处理异常 ...
该异常处理语法规则是:
执行try下的语句,如果引发异常,则执行过程会跳到第一个except语句。
如果第一个except中定义的异常与引发的异常匹配,则执行该except中的语句。
如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制。
如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。
示例代码:
1 2 3 4 5 6 7 8 9 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : res = int(a) / int(b) print(f'{a} / {b} = {res} ' ) except ZeroDivisionError as e: print(f'除数不能为0, 异常信息为:{e} ' ) except ValueError as e: print(f'输入的是无效数字, 异常信息为:{e} ' )
3. 多重异常捕获 如果多个except代码块的异常处理过程类似,可以合并处理,这就是多重异常捕获。基本语法如下:
1 2 3 4 try : 可能会引发异常的语句 except (异常类型1 ,异常类型2 ,...): 处理异常
示例代码:
1 2 3 4 5 6 7 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : res = int(a) / int(b) print(f'{a} / {b} = {res} ' ) except (ZeroDivisionError,ValueError) as e: print(f'异常发生, 异常信息为:{e} ' )
4. try except语句嵌套 try-except语句还可以嵌套, 语法格式如下:
1 2 3 4 5 6 7 8 try : 可能会引发异常的语句 try : 可能会引发异常的语句 except 异常类型1 : 处理异常 except 异常类型2 : 处理异常
修改示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : n1 = int(a) n2 = int(b) try : res = a / a print(f'{a} / {b} = {res} ' ) except ZeroDivisionError as e: print(f'除数不能为0, 异常信息为:{e} ' ) except ValueError as e: print(f'输入的是无效数字, 异常信息为:{e} ' )
5. 万能异常 在python的异常中,有一个万能异常:Exception,有时我们不知道具体的错误类型,但又为了防止程序报错而阻塞,可以使用万能异常Exception处理,他可以捕获任意异常,即:
1 2 3 4 5 6 7 8 9 try : 可能会引发异常的语句 except 异常类型1 : 处理异常 except 异常类型2 : 处理异常 ... except Exception(万能异常,可匹配所有异常): 处理异常
示例代码如下:
1 2 3 4 5 6 7 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : res = int(a) / int(b) print(f'{a} / {b} = {res} ' ) except Exception as e: print(f'异常发生, 异常信息为:{e} ' )
四、finally和else try/except 语句还有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。else 子句将在 try 子句没有发生任何异常的时候执行。如果判断完没有某些异常之后还想做其他事,就可以使用下面这样的else语句。
1 2 3 4 5 6 7 8 9 try : 可能会引发异常的语句 except 异常类型1 : 处理异常 except 异常类型2 : 处理异常 ... else : 代码
示例代码:
1 2 3 4 5 6 7 8 9 10 11 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : res = int(a) / int(b) except ZeroDivisionError as e: print(f'除数不能为0, 异常信息为:{e} ' ) except ValueError as e: print(f'输入的是无效数字, 异常信息为:{e} ' ) else : print(f'{a} / {b} = {res} ' )
有时在try-except语句中会占用一些资源, 例如打开的文件、 网络连接、 打开的数据库及数据结果集等都会占用计算机资源, 需要程序员释放这些资源。 为了确保这些资源能够被释放, 可以使用finally代码块。在try-except语句后面还可以跟一个finally代码块, 语法如下。
1 2 3 4 5 6 7 8 9 10 11 try : 可能会引发异常的语句 except 异常类型1 : 处理异常 except 异常类型2 : 处理异常 ... else : 代码块 finally : 代码块
无论是try代码块正常结束还是except代码块异常结束,都会执行finally代码块。
1 2 3 4 5 6 7 8 9 10 11 a = input('请输入1个数字:' ) b = input('请输入另1个数字:' ) try : res = int(a) / int(b) print(f'{a} / {b} = {res} ' ) except ZeroDivisionError as e: print(f'除数不能为0, 异常信息为:{e} ' ) except ValueError as e: print(f'输入的是无效数字, 异常信息为:{e} ' ) finally : print('释放资源...' )
五、抛出异常 Python 使用 raise 语句抛出一个指定的异常。
raise语法格式如下:
1 raise [Exception [, args [, traceback]]]
以下实例如果 x 大于 5 就触发异常:
1 2 3 x = 10 if x > 5 : raise Exception('x 不能大于 5。x 的值为: {}' .format(x))
执行以上代码会触发异常:
1 2 3 4 Traceback (most recent call last): File "test.py" , line 3 , in <module> raise Exception('x 不能大于 5。x 的值为: {}' .format(x)) Exception: x 不能大于 5 。x 的值为: 10
raise 唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。
如果你只想知道这是否抛出了一个异常,并不想去处理它,那么一个简单的 raise 语句就可以再次把它抛出。
1 2 3 4 5 6 7 8 9 10 try : raise NameError('HiThere' ) except NameError: print('An exception flew by!' ) raise An exception flew by! Traceback (most recent call last): File "<stdin>", line 2, **in** ? NameError: HiThere
六、自定义异常 有时候,为了提高代码的可重用性,自己编写了一下代码类库,自己编写了一些异常类。实现自定义异常类,需要继承Exception或其子类,之前我们遇到的ZeroDivisionError
和ValueError
异常类都输Exception的子类。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ShortInputException (Exception) : def __init__ (self,length,atleast) : self.length = length self.atleast = atleast while True : try : a = input("输入一个字符串:" ) if len(a) < 3 : raise ShortInputException(len(a),3 ) except ShortInputException as result: print("ShortInputException:输入的长度是%d,长度至少是:%d" %(result.length,result.atleast)) break else : print("没有异常" )
1 2 输入一个字符串:12 ShortInputException:输入的长度是2 ,长度至少是:3
当创建一个模块有可能抛出多种不同的异常时,一种通常的做法是为这个包建立一个基础异常类,然后基于这个基础类为不同的错误情况创建不同的子类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Error (Exception) : """Base class for exceptions in this module.""" pass class InputError (Error) : """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__ (self, expression, message) : self.expression = expression self.message = message class TransitionError (Error) : """Raised when an operation attempts a state transition that's not allowed. Attributes: previous -- state at beginning of transition next -- attempted new state message -- explanation of why the specific transition is not allowed """ def __init__ (self, previous, next, message) : self.previous = previous self.next = next self.message = message
大多数的异常的名字都以”Error”结尾,就跟标准的异常命名一样。
七、打印异常信息的方式 方式一:直接打印异常 1 2 3 4 5 6 try : res = 2 / 0 print(res) except Exception as e: print('str: ' , e) print('repr: ' , repr(e))
打印的异常信息不够详细,对错误追踪没有多大帮助。这时候异常堆栈信息就派上用场了。
方式二:通过logging
模块打印异常 1 2 3 4 5 6 7 8 9 10 11 12 try : res = 2 / 0 print(res) except Exception as e: logging.exception(e) """ ERROR:root:division by zero Traceback (most recent call last): File "D:/python/Python之路/10 异常处理/打印异常信息.py", line 21, in method2 res = 2 / 0 ZeroDivisionError: division by zero """
从异常堆栈信息中我们可以不费力气就找出错误代码是哪一行。
方式三:通过tracback
模块打印异常 1 2 3 4 5 6 7 8 9 10 11 try : res = 2 / 0 print(res) except Exception: traceback.print_exc() """ Traceback (most recent call last): File "D:/python/Python之路/10 异常处理/打印异常信息.py", line 36, in method3 res = 2 / 0 ZeroDivisionError: division by zero """
我们发现系统默认的异常打印方式就是tracback
方式。
Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。
断言可以在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况,例如我们的代码只能在 Linux 系统下运行,可以先判断当前系统是否符合条件。
语法格式如下:
等价于:
1 2 if not expression: raise AssertionError
assert 后面也可以紧跟参数:
1 assert expression [, arguments]
等价于:
1 2 if not expression: raise AssertionError(arguments)
以下为 assert 使用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 >>> assert True >>> assert False Traceback (most recent call last): File "<stdin>" , line 1 , in <module> AssertionError >>> assert 1 ==1 >>> assert 1 ==2 Traceback (most recent call last): File "<stdin>" , line 1 , in <module> AssertionError >>> assert 1 ==2 , '1 不等于 2' Traceback (most recent call last): File "<stdin>" , line 1 , in <module> AssertionError: 1 不等于 2 >>>
以下实例判断当前系统是否为 Linux,如果不满足条件则直接触发异常,不必执行接下来的代码:
1 2 3 4 import sysassert ('linux' in sys.platform), "该代码只能在 Linux 下执行"
八、简单的小结 程序在运行时可能遭遇无法预料的异常状况,可以使用Python的异常机制来处理这些状况。
Python的异常机制主要包括try
、except
、else
、finally
和raise
这五个核心关键字。
try
后面的except
语句不是必须的,finally
语句也不是必须的,但是二者必须要有一个;except
语句可以有一个或多个,多个except
会按照书写的顺序依次匹配指定的异常,如果异常已经处理就不会再进入后续的except
语句;except
语句中还可以通过元组同时指定多个异常类型进行捕获;except
语句后面如果不指定异常类型,则默认捕获所有异常;捕获异常后可以使用raise
要再次抛出,但是不建议捕获并抛出同一个异常;不建议在不清楚逻辑的情况下捕获所有异常,这可能会掩盖程序中严重的问题。
最后强调一点,不要使用异常机制来处理正常业务逻辑或控制程序流程,简单的说就是不要滥用异常机制。