2024年最新【Python学习教程】Python异常处理机制_avoid division byzero


这种情况下,对于 try 块中可能出现的任何异常,Python 解释器都会交给仅有的这个 except 块处理,因为它的参数是 Exception,表示可以接收任何类型的异常。



> 
> 注意,对于可以接收任何异常的 except 来说,其后可以跟 Exception,也可以不跟任何参数,但表示的含义都是一样的。
> 
> 
> 


这里就要详细介绍一下 Exception。要知道,为了表示程序中可能出现的各种异常,Python 提供了大量的异常类,这些异常类之间有严格的继承关系,图 1 显示了 Python 的常见异常类之间的继承关系。


![Python 的常见异常类之间的继承关系](https://img-blog.csdnimg.cn/img_convert/09a0ee5a8ff42b44bb3609636ff6615f.gif)  
 图 1 Python 的常见异常类之间的继承关系


从图 1 中可以看出,BaseException 是 Python 中所有异常类的基类,但对于我们来说,最主要的是 Exception 类,因为程序中可能出现的各种异常,都继承自 Exception。



> 
> 因此,如果用户要实现自定义异常,不应该继承 BaseException ,而应该继承 Exception 类。关于如何自定义一个异常类,可阅读《[Python自定义异常类]( )》一节。
> 
> 
> 


当 try 块捕获到异常对象后,Python 解释器会拿这个异常类型依次和各个 except 块指定的异常类进行比较,如果捕获到的这个异常类,和某个 except 块后的异常类一样,又或者是该异常类的子类,那么 Python 解释器就会调用这个 except 块来处理异常;反之,Python 解释器会继续比较,直到和最后一个 except 比较完,如果没有比对成功,则证明该异常无法处理。


图 2 演示了位于 try 块中的程序发生异常时,从捕获异常到处理异常的整个流程。


![Python 异常捕获流程示意图](https://img-blog.csdnimg.cn/img_convert/9045f615c2b321d3ca1daf052590bc53.gif)  
 图 2 Python 异常捕获流程示意图


下面看几个简单的异常捕获的例子:



try:
a = int(input(“输入 a:”))
b = int(input(“输入 b:”))
print( a/b )
except ValueError:
print(“数值错误:程序只能接收整数参数”)
except ArithmeticError:
print(“算术错误”)
except Exception:
print(“未知异常”)


该程序中,根据用户输入 a 和 b 值的不同,可能会导致 ValueError、ArithmeticError 异常:


1. 如果用户输入的 a 或者 b 是其他字符,而不是数字,会发生 ValueError 异常,try 块会捕获到该类型异常,同时 Python 解释器会调用第一个 except 块处理异常;
2. 如果用户输入的 a 和 b 是数字,但 b 的值为 0,由于在进行除法运算时除数不能为 0,因此会发生 ArithmeticError 异常,try 块会捕获该异常,同时 Python 解释器会调用第二个 except 块处理异常;
3. 当然,程序运行过程中,还可能由于其他因素出现异常,try 块都可以捕获,同时 Python 会调用最后一个 except 块来处理。



> 
> 当一个 try 块配有多个 except 块时,这些 except 块应遵循这样一个排序规则,即可处理全部异常的 except 块(参数为 Exception,也可以什么都不写)要放到所有 except 块的后面,且所有父类异常的 except 块要放到子类异常的 except 块的后面。
> 
> 
> 


## Python try except else详解


在原本的`try except`结构的基础上,[Python]( ) 异常处理机制还提供了一个 else 块,也就是原有 try except 语句的基础上再添加一个 else 块,即`try except else`结构。


使用 else 包裹的代码,只有当 try 块没有捕获到任何异常时,才会得到执行;反之,如果 try 块捕获到异常,即便调用对应的 except 处理完异常,else 块中的代码也不会得到执行。


举个例子:



try:
result = 20 / int(input(‘请输入除数:’))
print(result)
except ValueError:
print(‘必须输入整数’)
except ArithmeticError:
print(‘算术错误,除数不能为 0’)
else:
print(‘没有出现异常’)
print(“继续执行”)


可以看到,在原有 try except 的基础上,我们为其添加了 else 块。现在执行该程序:


请输入除数:4  
 5.0  
 没有出现异常  
 继续执行


如上所示,当我们输入正确的数据时,try 块中的程序正常执行,Python 解释器执行完 try 块中的程序之后,会继续执行 else 块中的程序,继而执行后续的程序。


读者可能会问,既然 Python 解释器按照顺序执行代码,那么 else 块有什么存在的必要呢?直接将 else 块中的代码编写在 try except 块的后面,不是一样吗?


当然不一样,现在再次执行上面的代码:


请输入除数:a  
 必须输入整数  
 继续执行


可以看到,当我们试图进行非法输入时,程序会发生异常并被 try 捕获,Python 解释器会调用相应的 except 块处理该异常。但是异常处理完毕之后,Python 解释器并没有接着执行 else 块中的代码,而是跳过 else,去执行后续的代码。


也就是说,else 的功能,只有当 try 块捕获到异常时才能显现出来。在这种情况下,else 块中的代码不会得到执行的机会。而如果我们直接把 else 块去掉,将其中的代码编写到 try except 的后面:



try:
result = 20 / int(input(‘请输入除数:’))
print(result)
except ValueError:
print(‘必须输入整数’)
except ArithmeticError:
print(‘算术错误,除数不能为 0’)
print(‘没有出现异常’)
print(“继续执行”)


程序执行结果为:


请输入除数:a  
 必须输入整数  
 没有出现异常  
 继续执行


可以看到,如果不使用 else 块,try 块捕获到异常并通过 except 成功处理,后续所有程序都会依次被执行。


## Python try except finally:资源回收


[Python]( ) 异常处理机制还提供了一个 finally 语句,通常用来为 try 块中的程序做扫尾清理工作。



> 
> 注意,和 else 语句不同,finally 只要求和 try 搭配使用,而至于该结构中是否包含 except 以及 else,对于 finally 不是必须的(else 必须和 try except 搭配使用)。
> 
> 
> 


在整个异常处理机制中,finally 语句的功能是:无论 try 块是否发生异常,最终都要进入 finally 语句,并执行其中的代码块。


基于 finally 语句的这种特性,在某些情况下,当 try 块中的程序打开了一些物理资源(文件、数据库连接等)时,由于这些资源必须手动回收,而回收工作通常就放在 finally 块中。



> 
> Python 垃圾回收机制,只能帮我们回收变量、类对象占用的内存,而无法自动完成类似关闭文件、数据库连接等这些的工作。
> 
> 
> 


读者可能会问,回收这些物理资源,必须使用 finally 块吗?当然不是,但使用 finally 块是比较好的选择。首先,try 块不适合做资源回收工作,因为一旦 try 块中的某行代码发生异常,则其后续的代码将不会得到执行;其次 except 和 else 也不适合,它们都可能不会得到执行。而 finally 块中的代码,无论 try 块是否发生异常,该块中的代码都会被执行。


举个例子:



try:
a = int(input(“请输入 a 的值:”))
print(20/a)
except:
print(“发生异常!”)
else:
print(“执行 else 块中的代码”)
finally :
print(“执行 finally 块中的代码”)


运行此程序:


请输入 a 的值:4  
 5.0  
 执行 else 块中的代码  
 执行 finally 块中的代码


可以看到,当 try 块中代码为发生异常时,except 块不会执行,else 块和 finally 块中的代码会被执行。


再次运行程序:


请输入 a 的值:a  
 发生异常!  
 执行 finally 块中的代码


可以看到,当 try 块中代码发生异常时,except 块得到执行,而 else 块中的代码将不执行,finally 块中的代码仍然会被执行。


finally 块的强大还远不止此,即便当 try 块发生异常,且没有合适和 except 处理异常时,finally 块中的代码也会得到执行。例如:



try:
#发生异常
print(20/0)
finally :
print(“执行 finally 块中的代码”)


程序执行结果为:


执行 finally 块中的代码  
 Traceback (most recent call last):  
 File “D:\python3.6\1.py”, line 3, in   
 print(20/0)  
 ZeroDivisionError: division by zero


可以看到,当 try 块中代码发生异常,导致程序崩溃时,在崩溃前 Python 解释器也会执行 finally 块中的代码。


## Python异常处理机制结构详解


到本节为止,读者已经学习了整个 Python 的异常处理机制的结构,接下来带领大家回顾一下,在此过程还会讲解一些新的知识。


首先,Python 完整的异常处理语法结构如下:


try:  
 #业务实现代码  
 except Exception1 as e:  
 #异常处理块1  
 …  
 except Exception2 as e:  
 #异常处理块2  
 …  
 #可以有多个 except  
 …  
 else:  
 #正常处理块  
 finally :  
 #资源回收块  
 …


整个异常处理结构的执行过程,如图 1 所示。


![img](https://img-blog.csdnimg.cn/img_convert/cbab0302b8de620933de51b34f44a869.png)  
 图 1 异常处理语句块的执行流程


注意,在整个异常处理结构中,只有 try 块是必需的,也就是说:


* 如果没有 try 块,则不能有后面的 except 块、else 块和 finally 块。但是也不能只使用 try 块,要么使用 try except 结构,要么使用 try finally 结构;
* except 块、else 块、finally 块都是可选的,当然也可以同时出现;
* 可以有多个 except 块,但捕获父类异常的 except 块应该位于捕获子类异常的 except 块的后面;
* 多个 except 块必须位于 try 块之后,finally 块必须位于所有的 except 块之后。
* 要使用 else 块,其前面必须包含 try 和 except。


其中,很多初学者分不清 finally 和 else 的区别,这里着重说一下。else 语句块只有在没有异常发生的情况下才会执行,而 finally 语句则不管异常是否发生都会执行。不仅如此,无论是正常退出、遇到异常退出,还是通过 break、continue、return 语句退出,finally 语句块都会执行。


注意,如果程序中运行了强制退出 Python 解释器的语句(如 os.\_exit(1) ),则 finally 语句将无法得到执行。例如:



import os
try:
os._exit(1)
finally:
print(“执行finally语句”)


运行程序,没有任何输出。因此,除非在 try 块、except 块中调用了退出 Python 解释器的方法,否则不管在 try 块、except 块中执行怎样的代码,出现怎样的情况,异常处理的 finally 块总会被执行。


另外在通常情况下,不要在 finally 块中使用如 return 或 raise 等导致方法中止的语句(raise 语句将在后面介绍),一旦在 finally 块中使用了 return 或 raise 语句,将会导致 try 块、except 块中的 return、raise 语句失效。看如下程序:



def test():
try:
# 因为finally块中包含了return语句
# 所以下面的return语句失去作用
return True
finally:
return False
print(test())


上面程序在 finally 块中定义了一条 return False 语句,这将导致 try 块中的 return true 失去作用。运行上面程序,输出结果为:


False


同样,如果 Python 程序在执行 try 块、except 块包含有 return 或 raise 语句,则 Python 解释器执行到该语句时,会先去查找 finally 块,如果没有 finally 块,程序才会立即执行 return 或 raise 语句;反之,如果找到 finally 块,系统立即开始执行 finally 块,只有当 finally 块执行完成后,系统才会再次跳回来执行 try 块、except 块里的 return 或 raise 语句。


但是,如果在 finally 块里也使用了 return 或 raise 等导致方法中止的语句,finally 块己经中止了方法,系统将不会跳回去执行 try 块、except 块里的任何代码。



> 
> 尽量避免在 finally 块里使用 return 或 raise 等导致方法中止的语句,否则可能出现一些很奇怪的情况。
> 
> 
> 


## Python logging模块用法快速攻略


无论使用哪种编程语言,最常用的调试代码的方式是:使用输出语句(比如 C 语言中使用 printf,Python 中使用 print() 函数)输出程序运行过程中一些关键的变量的值,查看它们的值是否正确,从而找到出错的地方。这种调试方法最大的缺点是,当找到问题所在之后,需要再将用于调试的输出语句删掉。


在 Python 中,有一种比频繁使用 print() 调试程序更简便的方法,就是使用 logging 模块,该模块可以很容易地创建自定义的消息记录,这些日志消息将描述程序执行何时到达日志函数调用,并列出指定的任何变量当时的值。


启用 logging 模块很简单,直接将下面的代码复制到程序开头:



import logging
logging.basicConfig(level=logging.DEBUG, format=’ %(asctime)s - %(levelname)s - %(message)s’)


读者不需要关心这两行代码的具体工作原理,但基本上,当 Python 记录一个事件的日志时,它会创建一个 LogRecord 对象,保存关于该事件的信息。


假如我们编写了如下一个函数,其设计的初衷是用来计算一个数的阶乘,但该函数有些问题,需要调试:



import logging
logging.basicConfig(level=logging.DEBUG, format=’ %(asctime)s - %(levelname)s - %(message)s’)
logging.debug(‘Start of program’)
def factorial(n):
logging.debug(‘Start of factorial(%s%%)’ % (n))
total = 1
for i in range(n + 1):
total *= i
logging.debug('i is ’ + str(i) + ', total is ’ + str(total))
logging.debug(‘End of factorial(%s%%)’ % (n))
return total
print(factorial(5))
logging.debug(‘End of program’)


运行结果为:


2019-09-11 14:14:56,928 - DEBUG - Start of program  
 2019-09-11 14:14:56,945 - DEBUG - Start of factorial(5%)  
 2019-09-11 14:14:56,959 - DEBUG - i is 0, total is 0  
 2019-09-11 14:14:56,967 - DEBUG - i is 1, total is 0  
 2019-09-11 14:14:56,979 - DEBUG - i is 2, total is 0  
 2019-09-11 14:14:56,991 - DEBUG - i is 3, total is 0  
 2019-09-11 14:14:57,000 - DEBUG - i is 4, total is 0  
 2019-09-11 14:14:57,013 - DEBUG - i is 5, total is 0  
 2019-09-11 14:14:57,024 - DEBUG - End of factorial(5%)  
 0  
 2019-09-11 14:14:57,042 - DEBUG - End of program


可以看到,通过 logging.debug() 函数可以打印日志信息,这个 debug() 函数将调用 basicConfig() 打印一行信息,这行信息的格式是在 basicConfig() 函数中指定的,并且包括传递给 debug() 的消息。


分析程序的运行结果,factorial(5) 返回 0 作为 5 的阶乘的结果,这显然是不对的。for 循环应该用从 1 到 5 的数,乘以 total 的值,但 logging.debug() 显示的日志信息表明,i 变量从 0 开始,而不是 1。因为 0 乘任何数都是 0,所以接下来的迭代中,total 的值都是错的。日志消息提供了可以追踪的痕迹,帮助我们弄清楚程序运行过程哪里不对。


将代码行 for i in range(n + 1):改为 for i in range(1,n + 1):,再次运行程序,输出结果为:


2019-09-11 14:21:18,047 - DEBUG - Start of program  
 2019-09-11 14:21:18,067 - DEBUG - Start of factorial(5%)  
 2019-09-11 14:21:18,072 - DEBUG - i is 1, total is 1  
 2019-09-11 14:21:18,082 - DEBUG - i is 2, total is 2  
 2019-09-11 14:21:18,087 - DEBUG - i is 3, total is 6  
 2019-09-11 14:21:18,093 - DEBUG - i is 4, total is 24  
 2019-09-11 14:21:18,101 - DEBUG - i is 5, total is 120  
 2019-09-11 14:21:18,106 - DEBUG - End of factorial(5%)  
 120  
 2019-09-11 14:21:18,123 - DEBUG - End of program


### Python logging日志级别


“日志级别”提供了一种方式,按重要性对日志消息进行分类。5 个日志级别如表 1 所示,从最不重要到最重要。利用不同的日志函数,消息可以按某个级别记入日志。




| 级别 | 对应的函数 | 描述 |
| --- | --- | --- |
| DEBUG | logging.debug() | 最低级别,用于小细节,通常只有在诊断问题时,才会关心这些消息。 |
| INFO | logging.info() | 用于记录程序中一般事件的信息,或确认一切工作正常。 |
| WARNING | logging.warning() | 用于表示可能的问题,它不会阻止程序的工作,但将来可能会。 |
| ERROR | logging.error() | 用于记录错误,它导致程序做某事失败。 |
| CRITICAL | logging.critical() | 最高级别,用于表示致命的错误,它导致或将要导致程序完全停止工作。 |


日志消息将会作为一个字符串,传递给这些函数。另外,日志级别只是一种建议,归根到底还是由程序员自己来决定日志消息属于哪一种类型。


举个例子:


>>>import logging  
 >>> logging.basicConfig(level=logging.DEBUG, format=’ %(asctime)s - %(levelname)s - %(message)s’)  
 >>> logging.debug(‘Some debugging details.’)  
 2019-09-11 14:32:34,249 - DEBUG - Some debugging details.  
 >>> logging.info(‘The logging module is working.’)  
 2019-09-11 14:32:47,456 - INFO - The logging module is working.  
 >>> logging.warning(‘An error message is about to be logged.’)  
 2019-09-11 14:33:02,391 - WARNING - An error message is about to be logged.  
 >>> logging.error(‘An error has occurred.’)  
 2019-09-11 14:33:14,413 - ERROR - An error has occurred.  
 >>> logging.critical(‘The program is unable to recover!’)  
 2019-09-11 14:33:24,071 - CRITICAL - The program is unable to recover!


日志级别的好处在于,我们可以改变想看到的日志消息的优先级。比如说,向 basicConfig() 函数传入 logging.DEBUG 作为 level 关键字参数,这将显示所有级别为 DEBUG 的日志消息。当开发了更多的程序后,我们可能只对错误感兴趣,在这种情况下,可以将 basicConfig() 的 level 参数设置为 logging.ERROR,这将只显示 ERROR 和 CRITICAL 消息,跳过 DEBUG、INFO 和 WARNING 消息。


### Python logging禁用日志


在调试完程序后,可能并不希望所有这些日志消息出现在屏幕上,这时就可以使用 logging.disable() 函数禁用这些日志消息,从而不必进入到程序中,手工删除所有的日志调用。


logging.disable() 函数的用法是,向其传入一个日志级别,它会禁止该级别以及更低级别的所有日志消息。因此,如果想要禁用所有日志,只要在程序中添加 logging.disable(logging.CRITICAL) 即可,例如:


>>> import logging  
 >>> logging.basicConfig(level=logging.INFO, format=’ %(asctime)s - %(levelname)s - %(message)s’)  
 >>> logging.critical(‘Critical error! Critical error!’)  
 2019-09-11 14:42:14,833 - CRITICAL - Critical error! Critical error!  
 >>> logging.disable(logging.CRITICAL)  
 >>> logging.critical(‘Critical error! Critical error!’)  
 >>> logging.error(‘Error! Error!’)


因为 logging.disable() 将禁用它之后的所有消息,所以可以将其添加到程序中更接近 import logging 的位置,这样更容易找到它,方便根据需要注释掉它,或取消注释,从而启用或禁用日志消息。


### 将日志消息输出到文件中


虽然日志消息很有用,但它们可能塞满屏幕,让你很难读到程序的输出。考虑到这种情况,可以将日志信息写入到文件,既能使屏幕保持干净,又能保存信息,一举两得。


将日志消息输出到文件中的实现方法很简单,只需要设置 logging.basicConfig() 函数中的 filename 关键字参数即可,例如:


>>> import logging  
 >>> logging.basicConfig(filename=‘demo.txt’, level=logging.DEBUG, format=’%(asctime)s - %(levelname)s - %(message)s’)


此程序中,将日志消息存储到了 demo.txt 文件中,该文件就位于运行的程序文件所在的目录。


## Python assert调试程序


前面章节介绍了如何使用 IDLE 自身的调试工具调试程序,除此之外,Python 还提供了 assert 语句,也可以用来调试程序。


《[Python assert断言]( )》一节中,已经对 assert 的基本用法做了简单介绍,assert 语句的完整语法格式为:


assert 条件表达式 [,描述信息]


assert 语句的作用是:当条件表达式的值为真时,该语句什么也不做,程序正常运行;反之,若条件表达式的值为假,则 assert 会抛出 AssertionError 异常。其中,[,描述信息] 作为可选参数,用于对条件表达式可能产生的异常进行描述。


例如:



s_age = input(“请输入您的年龄:”)
age = int(s_age)
assert 20 < age < 80 , “年龄不在 20-80 之间”
print(“您输入的年龄在20和80之间”)


程序运行结果为:


请输入您的年龄:10  
 Traceback (most recent call last):  
 File “C:\Users\mengma\Desktop\1.py”, line 3, in   
 assert 20 < age < 80 , “年龄不在 20-80 之间”  
 AssertionError: 年龄不在 20-80 之间


通过运行结果可以看出,当 assert 中条件表达式的值为假时,程序将抛出异常,并附带异常的描述性信息,与此同时,程序立即停止执行。


通常情况下,assert 可以和 try except 异常处理语句配合使用,以前面代码为例:



try:
s_age = input(“请输入您的年龄:”)
age = int(s_age)
assert 20 < age < 80 , “年龄不在 20-80 之间”
print(“您输入的年龄在20和80之间”)
except AssertionError as e:
print(“输入年龄不正确”,e)


程序运行结果为:




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里无偿获取](https://bbs.csdn.net/topics/618317507)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值