09 python异常
1、异常是什么
- Python使用异常对象来表示异常状态,并在遇到错误时引发异常。
- 异常对象未被处理(或捕获)时,程序将终止并显示一条错误消息(traceback)。
- 每个异常都是某个类的实例。你能以各种方式引发和捕获这些实例,从而逮住错误并采取措施,而不是放任整个程序失败。
###四种子语句可以同时存在 try: 可能出现异常的代码 except NameError: 出现异常并被该except捕获时会执行的代码 else: 没有异常时会执行的代码 finally: 无论是否出现异常都会执行的代码
2、捕获异常
- 对异常进行处理,通常称捕获异常。
- 使用try/except语句对异常进行捕获。
-
1)在except子句中,如果没有指定异常类,将捕获所有的异常。
2)可指定多个异常类,将它们放在元组中。
3)如果向except提供两个参数,第二个参数将关联到异常对象。
4)在同一条try/except语句中,可包含多个except子句,以便对不同的异常采取不同的措施。 -
1)仅当try子句中的代码引发异常时,并被相应except捕获时,才会执行相应的except子句中的代码。
2)当try子句中的代码没有引发异常时,所有的except都不会被执行。
-
- 异常从函数向外传播到调用函数的地方。如果在这里也没有被捕获,异常将向程序的最顶层传播。这意味着你可使用try/except来捕获他人所编写函数引发的异常。
try: 可能出现异常的代码 except NameError: 出现异常并被该except捕获时会执行的代码
2.1、单个except语句
- 一个except子句捕获一种异常
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) print('{} / {} = {}'.format(n1, n2, n1 / n2)) except ZeroDivisionError: print('除数不能为零') >>> #没有异常,不执行except子句 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> #有异常且是ZeroDivisionError异常,被except子句捕获,并执行except子句 输入第一个数字:1 输入第二个数字:0 <<< 除数不能为零 >>> #有异常但不是ZeroDivisionError异常,不能被except子句捕获,因此产生异常并终止程序 输入第一个数字:a <<< Traceback (most recent call last): File "D:/test/python/day/aa.py", line 2, in <module> n1 = int(input('输入第一个数字:')) ValueError: invalid literal for int() with base 10: 'a'
2.2、多个except子句
1、多个except子句捕获多种异常
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) print('{} / {} = {}'.format(n1, n2, n1 / n2)) except ZeroDivisionError: print('除数不能为零') except ValueError: print('输入的不是数字') >>> #没有异常,不执行except子句 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> #有异常且是ZeroDivisionError异常,被第一个except子句捕获,并执行该except子句中的代码 输入第一个数字:1 输入第二个数字:0 <<< 除数不能为零 >>> #有异常且是ValueError异常,被第二个except子句捕获,并执行该except子句中的代码 输入第一个数字:a <<< 输入的不是数字
2、将多个异常类放入元组中
- 可以将多种异常类放在一个元组中,这样使用一个except子句就可以捕获多种异常。
- 在except子句中,多个异常类两边的小括号不能省略。一种常见的错误就是省略小括号,这可能导致意想不到的结果。因为没有小括号except会将多个异常类当成多个参数而不是一个。
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) print('{} / {} = {}'.format(n1, n2, n1 / n2)) except (ZeroDivisionError, ValueError): print('有异常出现了!') >>> 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> 输入第一个数字:1 输入第二个数字:0 <<< 有异常出现了! >>> 输入第一个数字:a <<< 有异常出现了!
3、捕获对象
- 要在except子句中访问异常对象本身,可使用两个参数,第一个是异常类,第二个是异常类的对象。(请注意,即便是在你捕获多个异常时,也只向except提供了一个参数——一个元组。)
- 出现异常时可以让程序继续运行并记录错误。
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) print('{} / {} = {}'.format(n1, n2, n1 / n2)) except (ZeroDivisionError, ValueError) as err: print('有异常出现了!', err) >>> 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> 输入第一个数字:1 输入第二个数字:0 <<< 有异常出现了! division by zero >>> 输入第一个数字:a <<< 有异常出现了! invalid literal for int() with base 10: 'a'
2.3、一网打尽(捕获所有异常)
- 即使程序处理了好几种异常,但还是可能有一些漏网之鱼。
1、捕获所有的异常
- 捕获所有的异常,只需在except子句中不指定任何异常类。
- 注意:这样捕获所有的异常很危险,因为这不仅会隐藏你有心理准备的错误,还会隐藏你没有考虑过的错误。这还将捕获用户使用Ctrl + C终止执行的企图、调用函数sys.exit来终止执行的企图等。
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) print('{} / {} = {}'.format(n1, n2, n1 / n2)) except: print('出现异常了') >>> 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> 输入第一个数字:1 输入第二个数字:0 <<< 出现异常了 >>> 输入第一个数字:a <<< 出现异常了
2、捕获绝大部分异常
- 类Exception是大多数异常的父类,因此except Exception可以捕获大多数的异常。
- 在大多数情况下,选择是使用except Exception as err并对异常对象进行检查。这样做将让不是从Exception派生而来的为数不多的异常成为漏网之鱼,其中包括SystemExit和KeyboardInterrupt,因为它们是从BaseException(Exception的超类)派生而来的。
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) print('{} / {} = {}'.format(n1, n2, n1 / n2)) except Exception as err: print('出现的异常是:', err) >>> 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> 输入第一个数字:1 输入第二个数字:0 <<< 出现的异常是: division by zero >>> 输入第一个数字:a <<< 出现的异常是: invalid literal for int() with base 10: 'a'
3、try/except与else
try: 可能出现异常的代码 except NameError: 出现异常并被该except捕获时会执行的代码 else: 没有异常时会执行的代码
- 仅当try子句中的代码没有引发异常时,才会执行else子句。
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) result = n1 / n2 except Exception as err: print('出现的异常是:', err) else: print('{} / {} = {}'.format(n1, n2, result)) >>> 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> 输入第一个数字:1 输入第二个数字:0 <<< 出现的异常是: division by zero >>> 输入第一个数字:a <<< 出现的异常是: invalid literal for int() with base 10: 'a'
- 在函数中使用时,try子句中有return时,不论是否有异常都不会执行else子句(有异常时,依然执行except子句进行异常捕获,只是不执行else子句)。
示例1:函数,try中没有return
def func(): try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) result = n1 / n2 except Exception as err: print('出现的异常是:', err) else: return '{} / {} = {}'.format(n1, n2, result) hh = func() print(hh) >>> 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 >>> 输入第一个数字:1 输入第二个数字:0 <<< 出现的异常是: division by zero None >>> 输入第一个数字:a <<< 出现的异常是: invalid literal for int() with base 10: 'a' None
示例2:函数,try中使用return
def func(): try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) result = n1 / n2 return 0 except Exception as err: print('出现的异常是:', err) else: return '{} / {} = {}'.format(n1, n2, result) hh = func() print(hh) >>> 输入第一个数字:1 输入第二个数字:2 <<< 我是try中的return返回值 >>> 输入第一个数字:1 输入第二个数字:0 <<< 出现的异常是: division by zero None >>> 输入第一个数字:a <<< 出现的异常是: invalid literal for int() with base 10: 'a' None
4、try/finally
- finally子句非常适合用于确保文件或网络套接字等得以关闭以及程序崩溃后的清理工作。
try: 可能出现异常的代码 finally: 无论是否出现异常都会执行的代码
- 不论try子句中是否发异常,finally子句都将被执行。
try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) print('{} / {} = {}'.format(n1, n2, n1 / n2)) except Exception as err: print('出现的异常是:', err) finally: print('我在finally中') >>> 输入第一个数字:1 输入第二个数字:2 <<< 1 / 2 = 0.5 我在finally中 >>> 输入第一个数字:1 输入第二个数字:0 <<< 出现的异常是: division by zero 我在finally中 >>> 输入第一个数字:a <<< 出现的异常是: invalid literal for int() with base 10: 'a' 我在finally中
- 在函数中使用时,try子句中有return时,不论是否有异常都会执行finally子句。
- 若是finally中有return,函数的返回值只会是此return的返回值,而try和except中的return返回值被覆盖。
def func(): try: n1 = int(input('输入第一个数字:')) n2 = int(input('输入第二个数字:')) result = n1 / n2 return '{} / {} = {}'.format(n1, n2, result) except Exception as err: print('出现的异常是:', err) finally: print('我是在finally中') hh = func() print(hh) >>> 输入第一个数字:1 输入第二个数字:2 <<< 我是在finally中 1 / 2 = 0.5 #return的返回值 >>> 输入第一个数字:1 输入第二个数字:0 <<< 出现的异常是: division by zero 我是在finally中 None #return的返回值 >>> 输入第一个数字:a <<< 出现的异常是: invalid literal for int() with base 10: 'a' 我是在finally中 None #return的返回值
- 为何在try子句之前初始化x呢?因为如果不这样做,ZeroDivisionError将导致根本没有机会给它赋值,进而导致在finally子句中对其执行del时引发未捕获的异常。
x = None try: x = 1 / 0 finally: print('Cleaning up ...') del x
5、自主抛出异常
5.1、抛出异常
- 除了python解释器自动抛出的异常外,还可以手动的抛出异常。
- 要手动引发异常,可以使用raise语句,并将一个类(必须是Exception的子类)或实例作为参数。
- raise将类作为参数时,将自动创建一个实例。
###通用异常,没有指出出现了什么错误 >>> raise Exception Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> raise Exception Exception ###在Exception添加了错误信息 >>> raise Exception('hyperdrive overload') Traceback (most recent call last): File "<pyshell#1>", line 1, in <module> raise Exception('hyperdrive overload') Exception: hyperdrive overload
- 最常见的内置异常类,都可以用在raise语句中。
示例:
def register(): username = input('输入用户名:') if len(username) < 6: raise Exception('用户名的长度必须6位以上。') #抛出异常 else: print('输入的用户名是:', username) try: register() except Exception as err: print(err) print('注册失败!') else: print('注册成功!') >>> 输入用户名:admin123 <<< 输入的用户名是: admin123 注册成功! >>> 输入用户名:admin <<< 用户名的长度必须6位以上。 注册失败!
5.2、重新引发异常
1、捕获异常后,重新引发它
- 捕获异常后,如果要重新引发它(即继续向上传播),可在except子句中调用raise且不提供任何参数(也可显式地提供捕获到的异常,参见2.3捕获对象)。
class MuffledCalculator: muffled = False def calc(self, expr): try: return eval(expr) except ZeroDivisionError: if self.muffled: print('Division by zero is illegal') else: raise calculator = MuffledCalculator() print(calculator.calc('10 / 2')) #调用1 #关闭了抑制异常功能 # calculator.calc('10 / 0') #调用2 calculator.muffled = True #调用3 #开启了抑制异常功能 calculator.calc('10 / 0') <<< #调用1的结果 5.0 <<< #调用2的结果 Traceback (most recent call last): File "D:/test/python/day/aa.py", line 17, in <module> calculator.calc('10 / 0') File "D:/test/python/day/aa.py", line 6, in calc return eval(expr) File "<string>", line 1, in <module> ZeroDivisionError: division by zero <<< #调用3的结果 Division by zero is illegal
2、捕获异常后,引发别的异常
- 如果无法处理异常,在except子句中使用不带参数的raise通常是不错的选择,但有时你可能想引发别的异常。在这种情况下,导致进入except子句的异常将被作为异常上下文存储起来,并出现在最终的错误消息中。
try: 1/0 #引发了ZeroDivisionError异常 except ZeroDivisionError: #捕获了ZeroDivisionError异常,存储在异常上下文中 raise ValueError #抛出ValueError异常 <<< Traceback (most recent call last): File "D:/test/python/day/aa.py", line 2, in <module> 1/0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "D:/test/python/day/aa.py", line 4, in <module> raise ValueError ValueError
- 可以使用raise ... from ...语句来提供自己的异常上下文,也可使用None来禁用except捕获的异常上下文
示例1:
try: 1 / 0 except ZeroDivisionError: raise ValueError from '抛出ValueError异常' #提供自己的异常上下文 <<< Traceback (most recent call last): File "D:/test/python/day/aa.py", line 2, in <module> 1 / 0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "D:/test/python/day/aa.py", line 4, in <module> raise ValueError from '抛出ValueError异常' TypeError: exception causes must derive from BaseException
示例2:
try: 1 / 0 except ZeroDivisionError: raise ValueError from None #禁用except捕获的异常上下文 <<< Traceback (most recent call last): File "D:/test/python/day/aa.py", line 4, in <module> raise ValueError from None ValueError
6、自定义的异常类
- 那么如何创建异常类呢?和创建其他类一样,但必须直接或间接地继承Exception
class SomeCustomException(Exception): pass
7、异常和函数
- 异常和函数有着天然的联系。
- 如果不处理函数中引发的异常,它将向上传播到调用函数的地方。如果在那里也未得到处理,异常将继续传播,直至到达主程序(全局作用域)。如果主程序中也没有异常处理程序,程序将终止并显示栈跟踪消息。
def faulty(): raise Exception('Something is wrong') def ignore_exception(): faulty() def handle_exception(): try: faulty() except: print('Exception handled') handle_exception() ignore_exception() <<< #调用handle_exception()的输出 Exception handled <<< #调用ignore_exception()的输出 Traceback (most recent call last): File "D:/test/python/day/test.py", line 15, in <module> ignore_exception() File "D:/test/python/day/test.py", line 5, in ignore_exception faulty() File "D:/test/python/day/test.py", line 2, in faulty raise Exception('Something is wrong') Exception: Something is wrong
8、警告
- 如果想发出警告,指出情况偏离了正轨,可使用模块warnings中的函数warn。
- 警告只显示一次。如果再次运行最后一行代码,什么事情都不会发生。
>>> from warnings import warn >>> warn("I've got a bad feeling about this.") #默认警告。 显示警告信息,程序不停止运行 Warning (from warnings module): File "<pyshell#1>", line 1 UserWarning: I've got a bad feeling about this. >>> warn("I've got a bad feeling about this.") >>>
- 可使用模块warnings中的函数filterwarnings来抑制你发出的警告(或特定类型的警告),并指定要采取的措施,如"error"或"ignore"。
>>> from warnings import warn >>> from warnings import filterwarnings ###忽略警告。 不显示警告信息,程序不停止 >>> filterwarnings("ignore") >>> warn("Anyone out there?") ###升级警告。 显示警告信息,程序停止 >>> filterwarnings("error") >>> warn("Something is very wrong!") #再执行一次依然有告警 Traceback (most recent call last): File "<pyshell#14>", line 1, in <module> warn("Something is very wrong!") UserWarning: Something is very wrong!
- 发出警告时,可指定将引发的异常(即警告类别),但必须是Warning的子类。
>>> from warnings import warn >>> from warnings import filterwarnings >>> filterwarnings("error") >>> warn("This function is really old...", DeprecationWarning) Traceback (most recent call last): File "<pyshell#18>", line 1, in <module> warn("This function is really old...", DeprecationWarning) DeprecationWarning: This function is really old...
- 如果将警告转换为错误,将使用你指定的异常。另外,还可根据异常来过滤掉特定类型的警告。
>>> from warnings import warn >>> from warnings import filterwarnings >>> filterwarnings("ignore", category=DeprecationWarning) #忽略DeprecationWarning警告 >>> warn("Another deprecation warning.", DeprecationWarning) >>> warn("Something else.") Warning (from warnings module): File "<pyshell#5>", line 1 UserWarning: Something else.